document.addEventListener('DOMContentLoaded', () => { const AT_DID = 'did:plc:l37td5yhxl2irrzrgvei4qay'; // ── Music ────────────────────────────────────────────────────────────── async function fetchMusicStatus() { const url = `https://eurosky.social/xrpc/com.atproto.repo.getRecord?repo=${AT_DID}&collection=fm.teal.alpha.actor.status&rkey=self`; const el = document.getElementById('music-content'); try { const res = await fetch(url); if (!res.ok) throw new Error(res.status); const data = await res.json(); const item = data?.value?.item; if (!item) throw new Error('no item'); const artists = item.artists.map(a => a.artistName).join(', '); const trackUrl = item.originUrl || '#'; el.innerHTML = ` ${escapeHtml(item.trackName)} by ${escapeHtml(artists)} `; } catch { el.innerHTML = 'no recent activity'; } } // ── Latest original post ─────────────────────────────────────────────── // filter=posts_no_replies strips replies from the API response. // We then skip any reposts (reason.$type === reasonRepost) client-side, // since the filter param doesn't exclude those. async function fetchLatestPost() { // Fetch a small batch so we can skip any leading reposts const url = `https://public.api.bsky.app/xrpc/app.bsky.feed.getAuthorFeed?actor=${AT_DID}&limit=10&filter=posts_no_replies`; const contentEl = document.getElementById('post-content'); const dateEl = document.getElementById('post-date'); const mediaEl = document.getElementById('post-media'); try { const res = await fetch(url); if (!res.ok) throw new Error(res.status); const data = await res.json(); // Find first item that is neither a reply nor a repost const item = (data.feed || []).find(entry => !entry.reason && // no repost reason !entry.post.record.reply // no reply parent ); if (!item) throw new Error('no original posts found'); const post = item.post; const record = post.record; const rkey = post.uri.split('/').pop(); const postUrl = `https://bsky.app/profile/danielmorrisey.com/post/${rkey}`; contentEl.innerHTML = `
${escapeHtml(record.text || '')}
view on Bluesky `; if (record.createdAt) { dateEl.textContent = new Date(record.createdAt).toLocaleDateString('en-GB', { day: 'numeric', month: 'long', year: 'numeric' }); } // Images if (record.embed?.$type === 'app.bsky.embed.images') { record.embed.images.forEach(img => { const ref = img.image.ref.$link; const src = `https://imrs.danielmorrisey.com/https://luna.danielmorrisey.com/img/feed_fullsize/plain/${AT_DID}/${ref}@jpeg`; const el = document.createElement('img'); el.src = src; el.alt = img.alt || 'error: image failed to load, failed to reach luna.danielmorrisey.com and/or imrs.danielmorrisey.com'; el.loading = 'lazy'; mediaEl.appendChild(el); }); } } catch { contentEl.innerHTML = 'couldn\'t load post'; } } // ── Helpers ──────────────────────────────────────────────────────────── function escapeHtml(str) { return str .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } fetchMusicStatus(); fetchLatestPost(); });