madebydanny.uk written in html, css, and a lot of JavaScript I don't understand
madeydanny.uk
html
css
javascript
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