My personal site hosted @ https://indexx.dev
at main 5.6 kB view raw
1import { useEffect, useState } from "react"; 2import AudioVisualizer from "./AudioVisualizer"; 3 4export default function Tealfm() { 5 const [data, setData] = useState(null); 6 const [error, setError] = useState(null); 7 const [isCurrentlyPlaying, setIsCurrentlyPlaying] = useState(false); 8 9 useEffect(() => { 10 const fetchStatus = async () => { 11 const repo = "did:plc:sfjxpxxyvewb2zlxwoz2vduw"; 12 const statusCollection = "fm.teal.alpha.actor.status"; 13 const lastPlayedCollection = "fm.teal.alpha.feed.play"; 14 15 try { 16 const statusUrl = 17 `https://pds.indexx.dev/xrpc/com.atproto.repo.getRecord?repo=${repo}&collection=${statusCollection}&rkey=self`; 18 const statusRes = await fetch(statusUrl); 19 20 if (!statusRes.ok) { 21 throw new Error(`HTTP ${statusRes.status} on status fetch`); 22 } 23 24 const statusData = await statusRes.json(); 25 const statusValue = statusData?.value; 26 const nowTimestamp = Math.floor(Date.now() / 1000); 27 28 const isExpired = statusValue?.expiry && 29 nowTimestamp > parseInt(statusValue.expiry, 10); 30 const isItemEmpty = !statusValue?.item?.trackName; 31 32 let status; 33 34 if (statusValue && !isExpired && !isItemEmpty) { 35 const latest = statusValue.item; 36 37 status = { 38 song: latest.trackName, 39 artist: latest.artists.map((artist) => 40 artist.artistName 41 ).join(", "), 42 createdAt: parseInt(statusValue.time, 10) * 1000, 43 link: latest.originUrl ?? "", 44 }; 45 setData(status); 46 setIsCurrentlyPlaying(true); 47 return; 48 } 49 50 console.log( 51 "Status expired or empty. Falling back to last played record.", 52 ); 53 54 const lastPlayedUrl = 55 `https://pds.indexx.dev/xrpc/com.atproto.repo.listRecords?repo=${repo}&collection=${lastPlayedCollection}&limit=1&reverse=false`; 56 const lastPlayedRes = await fetch(lastPlayedUrl); 57 58 if (!lastPlayedRes.ok) { 59 throw new Error( 60 `HTTP ${lastPlayedRes.status} on listRecords fetch`, 61 ); 62 } 63 64 const lastPlayedData = await lastPlayedRes.json(); 65 66 if ( 67 lastPlayedData.records && lastPlayedData.records.length > 0 68 ) { 69 const latest = lastPlayedData.records[0].value; 70 71 status = { 72 song: latest.trackName, 73 artist: latest.artists.map((artist) => 74 artist.artistName 75 ).join(", "), 76 albumArt: 77 `https://coverartarchive.org/release/${latest.releaseMbId}/front-500`, 78 createdAt: latest.playedTime, 79 link: latest.originUrl ?? "", 80 }; 81 setData(status); 82 setIsCurrentlyPlaying(false); 83 } else { 84 console.log("No records found in last played collection."); 85 setIsCurrentlyPlaying(false); 86 } 87 } catch (err) { 88 console.error("Fetch failed:", err); 89 setError(err.message); 90 setIsCurrentlyPlaying(false); 91 } 92 }; 93 94 fetchStatus(); 95 }, []); 96 97 if (error) return <span>Error: {error}</span>; 98 if (!data) return null; 99 100 let timeAgo = ""; 101 let oldStatusClasses = ""; 102 103 const date = new Date(data.createdAt); 104 const now = new Date(); 105 const diff = now.getTime() - date.getTime(); 106 107 const minutes = Math.floor(diff / 60000); 108 const hours = Math.floor(minutes / 60); 109 const days = Math.floor(hours / 24); 110 111 if (days > 0) timeAgo = `${days} days ago`; 112 else if (hours > 0) timeAgo = `${hours} hours ago`; 113 else if (minutes > 0) timeAgo = `${minutes} minutes ago`; 114 else timeAgo = "just now"; 115 116 oldStatusClasses = days > 3 117 ? "opacity-75 text-decoration-line-through" 118 : ""; 119 120 return ( 121 <a 122 id="now-playing" 123 href={data.link} 124 target="_blank" 125 className={oldStatusClasses} 126 > 127 <AudioVisualizer isSilent={!isCurrentlyPlaying} /> 128 <div 129 style={{ 130 display: "flex", 131 flexDirection: "column", 132 gap: "2px", 133 width: "100%", 134 }} 135 > 136 <div style={{ fontWeight: "bold" }}>{data.song}</div> 137 <div style={{ fontSize: "0.9em", marginTop: "-5px" }}> 138 {data.artist} 139 </div> 140 <div 141 style={{ 142 fontSize: "0.8em", 143 opacity: 0.7, 144 marginTop: "-5px", 145 }} 146 > 147 <small style={{ fontSize: "0.7rem" }}> 148 ^ what I'm listening (or last listened) to 149 </small> 150 </div> 151 </div> 152 </a> 153 ); 154}