import { Elysia, t } from 'elysia'; import { staticPlugin } from '@elysiajs/static'; import path from 'path'; // Song data interface interface LastSong { name: string; artist: string; album: string; url: string; nowPlaying: boolean; image: any[]; } // Current song state let currentSong: LastSong | null = null; let lastSongId: string | null = null; // WebSocket connections const connections = new Set(); // Function to fetch song from Last.fm async function fetchLastFmSong(): Promise { const apiKey = Bun.env.LASTFM_API_KEY; const username = Bun.env.LASTFM_USERNAME; if (!apiKey || !username) { console.error('Last.fm API key or username not configured'); return null; } const url = `https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=${username}&api_key=${apiKey}&format=json&limit=1`; try { const response = await fetch(url); const data = await response.json(); const track = data.recenttracks.track[0]; return { name: track.name, artist: track.artist['#text'], album: track.album['#text'], url: track.url, nowPlaying: track['@attr']?.nowplaying === 'true', image: track.image || [], }; } catch (error) { console.error('Error fetching from Last.fm:', error); return null; } } // Function to check for song changes and notify clients async function checkAndUpdateSong(): Promise { const newSong = await fetchLastFmSong(); if (!newSong) return; // Create a unique identifier for the song const newSongId = `${newSong.artist}-${newSong.name}-${newSong.nowPlaying}`; // Check if song has changed if (newSongId !== lastSongId) { console.log('Song changed:', newSong.name, 'by', newSong.artist); currentSong = newSong; lastSongId = newSongId; // Notify all connected WebSocket clients const message = JSON.stringify({ type: 'song-update', data: newSong }); connections.forEach(ws => { try { ws.send(message); } catch (error) { console.error('Error sending to WebSocket client:', error); connections.delete(ws); } }); } } // Start polling Last.fm every second setInterval(checkAndUpdateSong, 1000); // Initial fetch checkAndUpdateSong(); // Determine static assets path - adjust for compiled executable const assetsPath = Bun.env.NODE_ENV === 'production' ? path.join(path.dirname(process.execPath || __dirname), 'dist') : 'dist'; console.log('Serving static assets from:', assetsPath); const app = new Elysia() .use(staticPlugin({ assets: assetsPath, prefix: '' })) .ws('/music', { message(ws, message) { // Handle incoming messages if needed console.log('Received message from client:', message); }, open(ws) { console.log('WebSocket client connected'); connections.add(ws); // Send current song to new client immediately if (currentSong) { const message = JSON.stringify({ type: 'song-update', data: currentSong }); ws.send(message); } }, close(ws) { console.log('WebSocket client disconnected'); connections.delete(ws); }, }) .listen({ port: Bun.env.PORT || 3000, hostname: Bun.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost' }); console.log( `🦊 Elysia is running at http://${app.server?.hostname}:${app.server?.port}` ); export type App = typeof app;