data endpoint for entity 90008 (aka. a website)

fallback to other covers if one is not available

ptr.pet b895d04b 7f61f12b

verified
0/0
Waiting for spindle ...
Changed files
+38 -47
src
lib
routes
(site)
+19 -32
src/lib/lastfm.ts
··· 9 9 name: string; 10 10 artist: string; 11 11 album: string; 12 - image: string | null; 12 + images: { 13 + mb: string | null; 14 + yt: string | null; 15 + }; 13 16 link: string | null; 14 17 when: number; 15 18 status: 'playing' | 'played'; ··· 27 30 }; 28 31 29 32 const getTrackCoverArt = (releaseMbId: string | null | undefined, originUrl: string | null | undefined) => { 30 - if (releaseMbId) return `https://coverartarchive.org/release/${releaseMbId}/front-250`; 31 - 32 - if (!originUrl) return null; 33 - let videoId: string | null = null; 33 + let mb: string | null = null; 34 + let yt: string | null = null; 35 + 36 + if (releaseMbId) mb = `https://coverartarchive.org/release/${releaseMbId}/front-250`; 34 37 35 38 try { 36 - if (originUrl.includes('youtube.com') || originUrl.includes('music.youtube.com')) { 37 - videoId = new URL(originUrl).searchParams.get('v'); 38 - } else if (originUrl.includes('youtu.be')) { 39 - videoId = originUrl.split('youtu.be/')[1]?.split('?')[0]; 39 + if (originUrl) { 40 + let videoId: string | null = null; 41 + if (originUrl.includes('youtube.com') || originUrl.includes('music.youtube.com')) { 42 + videoId = new URL(originUrl).searchParams.get('v'); 43 + } else if (originUrl.includes('youtu.be')) { 44 + videoId = originUrl.split('youtu.be/')[1]?.split('?')[0]; 45 + } 46 + if (videoId) yt = `https://img.youtube.com/vi/${videoId}/mqdefault.jpg`; 40 47 } 41 48 } catch { 42 - return null; 43 49 } 44 50 45 - if (!videoId) return null; 46 - return `https://img.youtube.com/vi/${videoId}/mqdefault.jpg`; 51 + return { mb, yt }; 47 52 }; 48 53 49 54 const joinArtists = (artists: any[]) => { ··· 66 71 if (statusData.value?.item) { 67 72 track = statusData.value.item; 68 73 if (track.playedTime) when = new Date(track.playedTime).getTime(); 69 - status = 'playing'; 74 + status = (Date.now() / 1000) >= track.expiry ? 'played' : 'playing'; 70 75 } 71 76 } 72 77 } catch (err) { 73 78 console.log('could not fetch teal status:', err); 74 79 } 75 80 76 - if (!track) { 77 - try { 78 - const playRes = await fetch( 79 - `${PDS}/xrpc/com.atproto.repo.listRecords?repo=${DID}&collection=fm.teal.alpha.feed.play&limit=1` 80 - ); 81 - if (playRes.ok) { 82 - const playData = await playRes.json(); 83 - if (playData.records.length > 0) { 84 - track = playData.records[0].value; 85 - if (track.playedTime) when = new Date(track.playedTime).getTime(); 86 - status = 'played'; 87 - } 88 - } 89 - } catch (err) { 90 - console.log('could not fetch teal history:', err); 91 - } 92 - } 93 - 94 81 if (!track) return; 95 82 96 83 const data: LastTrack = { 97 84 name: track.trackName, 98 85 artist: joinArtists(track.artists) ?? 'Unknown Artist', 99 86 album: track.releaseName ?? 'Unknown Album', 100 - image: getTrackCoverArt(track.releaseMbId, track.originUrl), 87 + images: getTrackCoverArt(track.releaseMbId, track.originUrl), 101 88 link: track.originUrl ?? null, 102 89 when: when, 103 90 status: status
+19 -15
src/routes/(site)/+page.svelte
··· 153 153 </div> 154 154 {/if} 155 155 {#if data.lastTrack} 156 + {@const images = data.lastTrack.images} 157 + {@const initialUrl = images.mb ?? images.yt} 156 158 <div class="flex flex-row gap-0.5 m-1.5 border-4 border-double bg-ralsei-black"> 157 159 <!-- svelte-ignore a11y_missing_attribute --> 158 - {#if data.lastTrack.image} 159 - <img 160 - class="border-4 w-[4.5rem] h-[4.5rem] object-cover" 161 - style="border-style: none double none none;" 162 - src={data.lastTrack.image} 163 - title={data.lastTrack.album} 164 - /> 165 - {:else} 166 - <img 167 - class="border-4 w-[4.5rem] h-[4.5rem] p-2" 168 - style="border-style: none double none none; image-rendering: pixelated;" 169 - src="/icons/cd_audio.webp" 170 - title={data.lastTrack.album} 171 - /> 172 - {/if} 160 + <img 161 + class="border-4 w-[4.5rem] h-[4.5rem] {initialUrl ? 'object-cover' : 'p-2'}" 162 + style="border-style: none double none none; {initialUrl ? '' : 'image-rendering: pixelated;'}" 163 + src={initialUrl ?? '/icons/cd_audio.webp'} 164 + title={data.lastTrack.album} 165 + onerror={(e) => { 166 + const img = e.currentTarget as HTMLImageElement; 167 + if (images.mb && img.src === images.mb && images.yt) 168 + img.src = images.yt; 169 + else { 170 + img.src = '/icons/cd_audio.webp'; 171 + img.classList.remove('object-cover'); 172 + img.classList.add('p-2'); 173 + img.style.imageRendering = 'pixelated'; 174 + } 175 + }} 176 + /> 173 177 <div class="flex flex-col max-w-[60ch] p-2"> 174 178 <p 175 179 class="text-shadow-green text-ralsei-green-light text-sm text-ellipsis text-nowrap overflow-hidden max-w-[50ch]"