madebydanny.uk written in html, css, and a lot of JavaScript I don't understand madeydanny.uk
html css javascript
at main 147 lines 4.2 kB view raw
1(() => { 2 const MUSIC_DID = 'did:plc:l37td5yhxl2irrzrgvei4qay'; 3 const PDS_BASE = 'https://selfhosted.social'; 4 5 const statusEl = document.getElementById('recent-status'); 6 const listEl = document.getElementById('recent-list'); 7 8 function setStatus(text) { 9 if (statusEl) statusEl.textContent = text || ''; 10 } 11 12 function formatDate(iso) { 13 if (!iso) return ''; 14 try { 15 const d = new Date(iso); 16 return d.toLocaleString(undefined, { 17 year: 'numeric', 18 month: 'short', 19 day: 'numeric', 20 hour: '2-digit', 21 minute: '2-digit' 22 }); 23 } catch { 24 return iso; 25 } 26 } 27 28 function buildArtworkUrl(play) { 29 if (!play) return ''; 30 // Prefer explicit artwork fields if they exist, else fall back to proxying the origin URL 31 const fromField = 32 play.artworkUrl || 33 play.artworkUrl100 || 34 (play.albumArt && play.albumArt.url); 35 36 if (fromField) return fromField; 37 38 if (play.originUrl) { 39 return 'https://imrs.madebydanny.uk/?url=' + encodeURIComponent(play.originUrl); 40 } 41 return ''; 42 } 43 44 function renderPlays(plays) { 45 if (!listEl) return; 46 listEl.innerHTML = ''; 47 48 if (!plays || !plays.length) { 49 setStatus('No recent plays found.'); 50 return; 51 } 52 53 setStatus(''); 54 55 plays.forEach((rec) => { 56 const value = rec.value || rec; // support both raw and listRecords-style 57 const artistsArr = Array.isArray(value.artists) ? value.artists : []; 58 const artists = artistsArr.map((a) => a.artistName || a.name).filter(Boolean).join(', '); 59 const title = value.trackName || 'Unknown track'; 60 const originUrl = value.originUrl || '#'; 61 const when = formatDate(value.playedTime || rec.indexedAt); 62 const artwork = buildArtworkUrl(value); 63 64 const item = document.createElement('div'); 65 item.className = 'recent-item'; 66 67 if (artwork) { 68 const img = document.createElement('img'); 69 img.className = 'recent-artwork'; 70 img.src = artwork; 71 img.alt = `${title} artwork`; 72 img.loading = 'lazy'; 73 item.appendChild(img); 74 } 75 76 const meta = document.createElement('div'); 77 meta.className = 'recent-meta'; 78 79 const titleEl = document.createElement('div'); 80 titleEl.className = 'recent-title'; 81 if (originUrl && originUrl !== '#') { 82 const link = document.createElement('a'); 83 link.href = originUrl; 84 link.target = '_blank'; 85 link.rel = 'noopener'; 86 link.textContent = title; 87 titleEl.appendChild(link); 88 } else { 89 titleEl.textContent = title; 90 } 91 92 const artistEl = document.createElement('div'); 93 artistEl.className = 'recent-artist'; 94 artistEl.textContent = artists || 'Unknown artist'; 95 96 const timeEl = document.createElement('div'); 97 timeEl.className = 'recent-time'; 98 timeEl.textContent = when; 99 100 meta.appendChild(titleEl); 101 meta.appendChild(artistEl); 102 meta.appendChild(timeEl); 103 104 item.appendChild(meta); 105 listEl.appendChild(item); 106 }); 107 } 108 109 async function fetchRecentPlays() { 110 setStatus('Loading recent plays…'); 111 112 try { 113 const url = 114 PDS_BASE + 115 '/xrpc/com.atproto.repo.listRecords' + 116 '?repo=' + 117 encodeURIComponent(MUSIC_DID) + 118 '&collection=' + 119 encodeURIComponent('fm.teal.alpha.feed.play') + 120 '&limit=50' + 121 '&reverse=true'; 122 123 const res = await fetch(url); 124 if (!res.ok) { 125 throw new Error('HTTP ' + res.status); 126 } 127 128 const data = await res.json(); 129 const records = Array.isArray(data.records) ? data.records : []; 130 131 // Sort by playedTime descending if present 132 records.sort((a, b) => { 133 const av = (a.value && a.value.playedTime) || a.indexedAt || ''; 134 const bv = (b.value && b.value.playedTime) || b.indexedAt || ''; 135 return (bv > av) ? 1 : (bv < av ? -1 : 0); 136 }); 137 138 renderPlays(records); 139 } catch (err) { 140 console.warn('Error loading recent plays', err); 141 setStatus('Error loading recent plays. Please try again later.'); 142 } 143 } 144 145 document.addEventListener('DOMContentLoaded', fetchRecentPlays); 146})(); 147