/* @licstart The following is the entire license notice for the JavaScript code in this file. Copyleft (🄯) 2025 James Osborne This file is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This file is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . @licend The above is the entire license notice for the JavaScript code in this file. */ (function () { 'use strict'; const CONFIG = { user: 'incentive.jb', smallLines: 7, lbBase: 'https://api.listenbrainz.org/1/', timeoutMs: 8000 }; const TOTAL = 1 + CONFIG.smallLines; const widget = document.getElementById('scrobble-widget'); if (!widget) return; const urlJoin = (a, b) => a.replace(/\/+$/, '') + '/' + b.replace(/^\/+/, ''); const HTML_ESC = { '&': '&', '<': '<', '>': '>', '"': '"' }; const esc = (s) => String(s ?? '').replace(/[&<>"]/g, (c) => HTML_ESC[c]); async function fetchListens() { const url = urlJoin( CONFIG.lbBase, `user/${encodeURIComponent(CONFIG.user)}/listens?count=${TOTAL}` ); const ctrl = new AbortController(); const timer = setTimeout(() => ctrl.abort(), CONFIG.timeoutMs); try { const res = await fetch(url, { cache: 'no-store', signal: ctrl.signal }); if (!res.ok) throw new Error(`HTTP ${res.status}`); const j = await res.json(); return (j && (j.payload?.listens || j.listens)) || []; } finally { clearTimeout(timer); } } fetchListens() .then((listens) => { if (!listens.length) { widget.textContent = 'No recent scrobbles.'; return; } // Normalize to { artist, title } safely const rows = listens.map((l) => { const tm = l.track_metadata || l.track || {}; const artist = tm.artist_name ?? tm.artist?.name ?? tm.artist ?? 'Unknown Artist'; const title = tm.track_name ?? tm.title ?? tm.track?.title ?? tm.track ?? 'Unknown Track'; return { artist: esc(artist), title: esc(title) }; }); const top = `
${rows[0].artist} ${rows[0].title}
`; const more = rows .slice(1, 1 + CONFIG.smallLines) .map( (r) => `
  • ${r.artist} ${r.title}
  • ` ) .join(''); widget.innerHTML = top + ``; }) .catch((err) => { console.warn('Scrobbles error:', err); widget.textContent = 'Scrobbles unavailable.'; }); })();