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