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}