fix: iTunes route order (lookup before proxy); stable cover art URLs for browser caching
This commit is contained in:
1 parent
09cec18b33
commit
21159ad2c5
3 files changed
+45
-24
No files matched your search
+24
-22
@@ -160,8 +160,32 @@ app.post('/api/spotify/track-artwork', async (req, res) => {
|
|||||||
res.json({ results });
|
res.json({ results });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ─── ITUNES METADATA LOOKUP (must be before the proxy route) ─────────────────
|
||||||
|
app.get('/api/itunes/lookup', async (req, res) => {
|
||||||
|
const { artist, title } = req.query;
|
||||||
|
if (!artist || !title) return res.status(400).json({ error: 'artist and title required' });
|
||||||
|
try {
|
||||||
|
const meta = await lookupItunesTrackMeta(artist, title);
|
||||||
|
if (!meta.album && !meta.genre && !meta.year) return res.json({ found: false });
|
||||||
|
res.json({
|
||||||
|
found: true,
|
||||||
|
album: meta.album || undefined,
|
||||||
|
year: meta.year || undefined,
|
||||||
|
trackNum: meta.trackNum || undefined,
|
||||||
|
totalTracks: meta.totalTracks || undefined,
|
||||||
|
genre: meta.genre || undefined,
|
||||||
|
coverUrl: meta.artworkUrl || undefined,
|
||||||
|
artist: meta.itunesArtistName || undefined,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[itunes/lookup]', e.message);
|
||||||
|
res.status(500).json({ error: e.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ─── ITUNES PROXY ─────────────────────────────────────────────────────────────
|
// ─── ITUNES PROXY ─────────────────────────────────────────────────────────────
|
||||||
// Browsers can't call itunes.apple.com directly (no CORS headers). Proxy it here.
|
// Browsers can't call itunes.apple.com directly (no CORS headers). Proxy it here.
|
||||||
|
// Note: /api/itunes/lookup is handled above — proxy only covers /search and id-based /lookup.
|
||||||
app.get('/api/itunes/:endpoint', (req, res) => {
|
app.get('/api/itunes/:endpoint', (req, res) => {
|
||||||
const allowed = new Set(['search', 'lookup']);
|
const allowed = new Set(['search', 'lookup']);
|
||||||
if (!allowed.has(req.params.endpoint)) return res.status(404).json({ error: 'not found' });
|
if (!allowed.has(req.params.endpoint)) return res.status(404).json({ error: 'not found' });
|
||||||
@@ -570,28 +594,6 @@ app.post('/api/soundcloud/resolve', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── ITUNES LOOKUP ───────────────────────────────────────────────────────────
|
|
||||||
app.get('/api/itunes/lookup', async (req, res) => {
|
|
||||||
const { artist, title } = req.query;
|
|
||||||
if (!artist || !title) return res.status(400).json({ error: 'artist and title required' });
|
|
||||||
try {
|
|
||||||
const meta = await lookupItunesTrackMeta(artist, title);
|
|
||||||
if (!meta.album && !meta.genre && !meta.year) return res.json({ found: false });
|
|
||||||
res.json({
|
|
||||||
found: true,
|
|
||||||
album: meta.album || undefined,
|
|
||||||
year: meta.year || undefined,
|
|
||||||
trackNum: meta.trackNum || undefined,
|
|
||||||
totalTracks: meta.totalTracks || undefined,
|
|
||||||
genre: meta.genre || undefined,
|
|
||||||
coverUrl: meta.artworkUrl || undefined,
|
|
||||||
artist: meta.itunesArtistName || undefined,
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
console.error('[itunes/lookup]', e.message);
|
|
||||||
res.status(500).json({ error: e.message });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// ─── MUSICBRAINZ LOOKUP ──────────────────────────────────────────────────────
|
// ─── MUSICBRAINZ LOOKUP ──────────────────────────────────────────────────────
|
||||||
app.get('/api/musicbrainz/lookup', async (req, res) => {
|
app.get('/api/musicbrainz/lookup', async (req, res) => {
|
||||||
|
|||||||
+18
-1
@@ -28,12 +28,29 @@ export async function getLyricsBySongId(songId) {
|
|||||||
} catch { return []; }
|
} catch { return []; }
|
||||||
}
|
}
|
||||||
export function downloadUrl(id) { return apiUrl('download', `&id=${id}`); }
|
export function downloadUrl(id) { return apiUrl('download', `&id=${id}`); }
|
||||||
|
// Stable salt derived from credentials so the browser HTTP cache survives page reloads.
|
||||||
|
// Random salt (apiUrl default) changes every session → cache miss every time → art re-fetches.
|
||||||
|
let _coverSalt = null, _coverToken = null;
|
||||||
|
function _coverAuth() {
|
||||||
|
if (!_coverSalt && S.cfg.user && S.cfg.pass) {
|
||||||
|
_coverSalt = md5(S.cfg.user + S.cfg.pass).slice(0, 8);
|
||||||
|
_coverToken = md5(S.cfg.pass + _coverSalt);
|
||||||
|
}
|
||||||
|
return _coverSalt ? `u=${encodeURIComponent(S.cfg.user)}&t=${_coverToken}&s=${_coverSalt}` : null;
|
||||||
|
}
|
||||||
export function coverUrl(id, size=300) {
|
export function coverUrl(id, size=300) {
|
||||||
if (!id) return null;
|
if (!id) return null;
|
||||||
const key = `${id}:${size}`;
|
const key = `${id}:${size}`;
|
||||||
if (!_coverUrlCache.has(key)) _coverUrlCache.set(key, apiUrl('getCoverArt', `&id=${id}&size=${size}`));
|
if (!_coverUrlCache.has(key)) {
|
||||||
|
const auth = _coverAuth();
|
||||||
|
const url = auth
|
||||||
|
? `${S.cfg.url}/rest/getCoverArt?${auth}&v=1.16.1&c=NovaSonic&f=json&id=${id}&size=${size}`
|
||||||
|
: apiUrl('getCoverArt', `&id=${id}&size=${size}`);
|
||||||
|
_coverUrlCache.set(key, url);
|
||||||
|
}
|
||||||
return _coverUrlCache.get(key);
|
return _coverUrlCache.get(key);
|
||||||
}
|
}
|
||||||
|
export function invalidateCoverCache() { _coverUrlCache.clear(); _coverSalt = null; _coverToken = null; }
|
||||||
|
|
||||||
// ─── NAVIDROME REST AUTH ───
|
// ─── NAVIDROME REST AUTH ───
|
||||||
export async function ndAuth() {
|
export async function ndAuth() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
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, ndAuth, fetchRomajiIndex, romajiConvert, romajiLookup, invalidateRomajiIndex } from './api.js';
|
import { api, coverUrl, ndAuth, fetchRomajiIndex, romajiConvert, romajiLookup, invalidateRomajiIndex, invalidateCoverCache } from './api.js';
|
||||||
import { THEMES, applyTheme, getCurrentThemeId } from './themes.js';
|
import { THEMES, applyTheme, getCurrentThemeId } from './themes.js';
|
||||||
// Circular imports — safe (only used inside function bodies)
|
// Circular imports — safe (only used inside function bodies)
|
||||||
import { playQueue, dlSongLocal, toggleStar, togglePlay, renderQueue, syncNpStar, updateStarBtn, npToggleStar, playCurrent, _syncNpDirect, setPlayIcon, nativeInsertAt, refreshDisplayMode } from './player.js';
|
import { playQueue, dlSongLocal, toggleStar, togglePlay, renderQueue, syncNpStar, updateStarBtn, npToggleStar, playCurrent, _syncNpDirect, setPlayIcon, nativeInsertAt, refreshDisplayMode } from './player.js';
|
||||||
@@ -78,7 +78,9 @@ export function saveSettings() {
|
|||||||
const pass = document.getElementById('s-pass').value;
|
const pass = document.getElementById('s-pass').value;
|
||||||
S.cfg.url = document.getElementById('s-url').value.trim().replace(/\/$/,'');
|
S.cfg.url = document.getElementById('s-url').value.trim().replace(/\/$/,'');
|
||||||
S.cfg.user = document.getElementById('s-user').value.trim();
|
S.cfg.user = document.getElementById('s-user').value.trim();
|
||||||
|
const credChanged = S.cfg.url !== document.getElementById('s-url').value.trim().replace(/\/$/,'') || !!pass;
|
||||||
if(pass) S.cfg.pass = pass;
|
if(pass) S.cfg.pass = pass;
|
||||||
|
if(credChanged) invalidateCoverCache();
|
||||||
S.cfg.lastfmKey = document.getElementById('s-lastfm').value.trim();
|
S.cfg.lastfmKey = document.getElementById('s-lastfm').value.trim();
|
||||||
S.cfg.backendUrl = document.getElementById('s-backend').value.trim().replace(/\/$/,'');
|
S.cfg.backendUrl = document.getElementById('s-backend').value.trim().replace(/\/$/,'');
|
||||||
S.cfg.showPlayCount = document.getElementById('s-show-playcount').classList.contains('on');
|
S.cfg.showPlayCount = document.getElementById('s-show-playcount').classList.contains('on');
|
||||||
|
|||||||
Reference in new issue
Block a user