All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m51s
135 lines
3.4 KiB
TypeScript
135 lines
3.4 KiB
TypeScript
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<any>();
|
|
|
|
// Function to fetch song from Last.fm
|
|
async function fetchLastFmSong(): Promise<LastSong | null> {
|
|
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<void> {
|
|
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;
|