diff --git a/src/lib/utils/albumEnricher.ts b/src/lib/utils/albumEnricher.ts index a5fb075..2bfdbb5 100644 --- a/src/lib/utils/albumEnricher.ts +++ b/src/lib/utils/albumEnricher.ts @@ -5,6 +5,18 @@ import { transformImages, mergeAppleMusicData } from './lastfmTransformers' import redis from '../../routes/api/redis-client' import { logger } from '$lib/server/logger' +// Type for cached recent tracks data +interface RecentTracksData { + tracks: Array<{ + name: string + artist: string + album?: string + date?: Date | string + [key: string]: unknown + }> + [key: string]: unknown +} + export class AlbumEnricher { private client: LastClient private cacheTTL = { @@ -116,7 +128,10 @@ export class AlbumEnricher { /** * Get Apple Music data for duration-based now playing detection */ - async getAppleMusicDataForNowPlaying(artistName: string, albumName: string): Promise { + async getAppleMusicDataForNowPlaying( + artistName: string, + albumName: string + ): Promise { const cacheKey = `apple:album:${artistName}:${albumName}` const cached = await redis.get(cacheKey) @@ -146,7 +161,7 @@ export class AlbumEnricher { /** * Cache recent tracks from Last.fm */ - async cacheRecentTracks(username: string, recentTracks: any): Promise { + async cacheRecentTracks(username: string, recentTracks: RecentTracksData): Promise { const cacheKey = `lastfm:recent:${username}` await redis.set(cacheKey, JSON.stringify(recentTracks), 'EX', this.cacheTTL.recentTracks) } @@ -154,15 +169,15 @@ export class AlbumEnricher { /** * Get cached recent tracks */ - async getCachedRecentTracks(username: string): Promise { + async getCachedRecentTracks(username: string): Promise { const cacheKey = `lastfm:recent:${username}` const cached = await redis.get(cacheKey) if (cached) { - const data = JSON.parse(cached) + const data = JSON.parse(cached) as RecentTracksData // Convert date strings back to Date objects if (data.tracks) { - data.tracks = data.tracks.map((track: any) => ({ + data.tracks = data.tracks.map((track) => ({ ...track, date: track.date ? new Date(track.date) : undefined })) diff --git a/src/lib/utils/lastfmStreamManager.ts b/src/lib/utils/lastfmStreamManager.ts index 9db193f..b655a1e 100644 --- a/src/lib/utils/lastfmStreamManager.ts +++ b/src/lib/utils/lastfmStreamManager.ts @@ -5,6 +5,24 @@ import { AlbumEnricher } from './albumEnricher' import { trackToAlbum, getAlbumKey } from './lastfmTransformers' import { logger } from '$lib/server/logger' +// Type for Last.fm recent tracks response +interface RecentTracksResponse { + tracks: Array<{ + name: string + album: { + name: string + mbid?: string + } + artist: { + name: string + } + nowPlaying?: boolean + date?: Date | { uts: number; '#text': string } + [key: string]: unknown + }> + [key: string]: unknown +} + export interface StreamState { lastNowPlayingState: Map lastAlbumOrder: string[] @@ -78,7 +96,7 @@ export class LastfmStreamManager { /** * Fetch fresh recent tracks from Last.fm (no cache) */ - private async fetchFreshRecentTracks(): Promise { + private async fetchFreshRecentTracks(): Promise { logger.music('debug', 'Fetching fresh Last.fm recent tracks for now playing detection') const recentTracksResponse = await this.client.user.getRecentTracks(this.username, { limit: 50, @@ -92,7 +110,10 @@ export class LastfmStreamManager { /** * Get recent albums from Last.fm */ - private async getRecentAlbums(limit: number, recentTracksResponse?: any): Promise { + private async getRecentAlbums( + limit: number, + recentTracksResponse?: RecentTracksResponse + ): Promise { // Use provided fresh data or fetch new if (!recentTracksResponse) { recentTracksResponse = await this.fetchFreshRecentTracks() @@ -124,7 +145,10 @@ export class LastfmStreamManager { /** * Update now playing status using the detector */ - private async updateNowPlayingStatus(albums: Album[], recentTracksResponse?: any): Promise { + private async updateNowPlayingStatus( + albums: Album[], + recentTracksResponse?: RecentTracksResponse + ): Promise { // Use provided fresh data or fetch new if (!recentTracksResponse) { recentTracksResponse = await this.fetchFreshRecentTracks() @@ -237,7 +261,7 @@ export class LastfmStreamManager { */ private async getNowPlayingUpdatesForNonRecentAlbums( recentAlbums: Album[], - recentTracksResponse?: any + recentTracksResponse?: RecentTracksResponse ): Promise { const updates: NowPlayingUpdate[] = [] diff --git a/src/lib/utils/lastfmTransformers.ts b/src/lib/utils/lastfmTransformers.ts index 2dc9fd1..db83fb6 100644 --- a/src/lib/utils/lastfmTransformers.ts +++ b/src/lib/utils/lastfmTransformers.ts @@ -1,6 +1,25 @@ import type { Album, AlbumImages } from '$lib/types/lastfm' import type { LastfmImage } from '@musicorum/lastfm/dist/types/packages/common' +// Type for Last.fm track data from API responses +interface LastfmTrack { + name: string + album: { + name: string + mbid?: string + } + artist: { + name: string + mbid?: string + } + images: LastfmImage[] + url?: string + nowPlaying?: boolean + date?: Date | string + mbid?: string + [key: string]: unknown +} + /** * Transform Last.fm image array into structured AlbumImages object */ @@ -43,7 +62,7 @@ export function getAlbumKey(artistName: string, albumName: string): string { /** * Transform track data into an Album object */ -export function trackToAlbum(track: any, rank: number): Album { +export function trackToAlbum(track: LastfmTrack, rank: number): Album { return { name: track.album.name, artist: { @@ -53,7 +72,7 @@ export function trackToAlbum(track: any, rank: number): Album { playCount: 1, images: transformImages(track.images), mbid: track.album.mbid || '', - url: track.url, + url: track.url || '', rank, isNowPlaying: track.nowPlaying || false, nowPlayingTrack: track.nowPlaying ? track.name : undefined, @@ -64,12 +83,12 @@ export function trackToAlbum(track: any, rank: number): Album { /** * Merge Apple Music data into an Album */ -export function mergeAppleMusicData(album: Album, appleMusicData: any): Album { +export function mergeAppleMusicData(album: Album, appleMusicData: Album['appleMusicData']): Album { return { ...album, images: { ...album.images, - itunes: appleMusicData.highResArtwork || album.images.itunes + itunes: appleMusicData?.highResArtwork || album.images.itunes }, appleMusicData } diff --git a/src/lib/utils/nowPlayingDetector.ts b/src/lib/utils/nowPlayingDetector.ts index fc75ab0..cbb1563 100644 --- a/src/lib/utils/nowPlayingDetector.ts +++ b/src/lib/utils/nowPlayingDetector.ts @@ -1,4 +1,5 @@ import { logger } from '$lib/server/logger' +import type { Album } from '$lib/types/lastfm' export interface TrackPlayInfo { albumName: string @@ -19,6 +20,29 @@ export interface NowPlayingResult { nowPlayingTrack?: string } +// Type for Last.fm track from recent tracks API +interface LastfmRecentTrack { + name: string + artist: { + name: string + } + album: { + name: string + } + nowPlaying?: boolean + date?: { + uts: number | string + '#text': string + } + [key: string]: unknown +} + +// Type for recent tracks response +interface RecentTracksResponse { + tracks: LastfmRecentTrack[] + [key: string]: unknown +} + // Configuration constants const SCROBBLE_LAG = 3 * 60 * 1000 // 3 minutes to account for Last.fm scrobble delay const TRACK_HISTORY_WINDOW = 60 * 60 * 1000 // Keep 1 hour of track history @@ -139,8 +163,11 @@ export class NowPlayingDetector { * Process now playing data from Last.fm API response */ processNowPlayingTracks( - recentTracksResponse: any, - appleMusicDataLookup: (artistName: string, albumName: string) => Promise + recentTracksResponse: RecentTracksResponse, + appleMusicDataLookup: ( + artistName: string, + albumName: string + ) => Promise ): Promise> { return this.detectNowPlayingAlbums(recentTracksResponse.tracks, appleMusicDataLookup) } @@ -149,8 +176,11 @@ export class NowPlayingDetector { * Detect which albums are currently playing from a list of tracks */ private async detectNowPlayingAlbums( - tracks: any[], - appleMusicDataLookup: (artistName: string, albumName: string) => Promise + tracks: LastfmRecentTrack[], + appleMusicDataLookup: ( + artistName: string, + albumName: string + ) => Promise ): Promise> { const albums: Map = new Map() let hasOfficialNowPlaying = false diff --git a/src/lib/utils/simpleLastfmStreamManager.ts b/src/lib/utils/simpleLastfmStreamManager.ts index 9879ced..cf1c312 100644 --- a/src/lib/utils/simpleLastfmStreamManager.ts +++ b/src/lib/utils/simpleLastfmStreamManager.ts @@ -5,6 +5,24 @@ import { AlbumEnricher } from './albumEnricher' import { trackToAlbum } from './lastfmTransformers' import { logger } from '$lib/server/logger' +// Type for recent tracks response +interface RecentTracksResponse { + tracks: Array<{ + name: string + album: { + name: string + mbid?: string + } + artist: { + name: string + } + nowPlaying?: boolean + date?: unknown + [key: string]: unknown + }> + [key: string]: unknown +} + export interface StreamUpdate { albums?: Album[] } @@ -90,7 +108,10 @@ export class SimpleLastfmStreamManager { /** * Get recent albums from Last.fm tracks */ - private async getRecentAlbums(limit: number, recentTracksResponse: any): Promise { + private async getRecentAlbums( + limit: number, + recentTracksResponse: RecentTracksResponse + ): Promise { const uniqueAlbums = new Map() for (const track of recentTracksResponse.tracks) { diff --git a/src/lib/utils/simpleNowPlayingDetector.ts b/src/lib/utils/simpleNowPlayingDetector.ts index eca5f36..e8bbc3d 100644 --- a/src/lib/utils/simpleNowPlayingDetector.ts +++ b/src/lib/utils/simpleNowPlayingDetector.ts @@ -1,6 +1,22 @@ import type { Album } from '$lib/types/lastfm' import { logger } from '$lib/server/logger' +// Type for Last.fm track from API +interface LastfmTrack { + name: string + album: { + name: string + } + artist: { + name: string + } + nowPlaying?: boolean + date?: { + uts: number | string + } + [key: string]: unknown +} + // Simple buffer time for tracks that might have paused/buffered const BUFFER_TIME_MS = 30000 // 30 seconds grace period @@ -27,8 +43,11 @@ export class SimpleNowPlayingDetector { */ async processAlbums( albums: Album[], - recentTracks: any[], - appleMusicDataLookup: (artistName: string, albumName: string) => Promise + recentTracks: LastfmTrack[], + appleMusicDataLookup: ( + artistName: string, + albumName: string + ) => Promise ): Promise { logger.music('debug', `Processing ${albums.length} albums with ${recentTracks.length} recent tracks`) @@ -59,15 +78,18 @@ export class SimpleNowPlayingDetector { // Fall back to duration-based detection logger.music('debug', 'Using duration-based detection') - + // Find the most recent track across all albums - let mostRecentTrack: any = null + let mostRecentTrack: LastfmTrack | null = null let mostRecentTime = new Date(0) - + for (const track of recentTracks) { - if (track.date && track.date > mostRecentTime) { - mostRecentTime = track.date - mostRecentTrack = track + if (track.date && typeof track.date === 'object' && 'uts' in track.date) { + const trackTime = new Date(Number(track.date.uts) * 1000) + if (trackTime > mostRecentTime) { + mostRecentTime = trackTime + mostRecentTrack = track + } } } @@ -82,26 +104,26 @@ export class SimpleNowPlayingDetector { } logger.music('debug', `Most recent track: "${mostRecentTrack.name}" by ${mostRecentTrack.artist.name} from ${mostRecentTrack.album.name}`) - logger.music('debug', `Scrobbled at: ${mostRecentTrack.date}`) - + logger.music('debug', `Scrobbled at: ${mostRecentTime}`) + // Check if the most recent track is still playing const albumKey = `${mostRecentTrack.artist.name}:${mostRecentTrack.album.name}` let isPlaying = false let playingTrack: string | undefined - + try { const appleMusicData = await appleMusicDataLookup( - mostRecentTrack.artist.name, + mostRecentTrack.artist.name, mostRecentTrack.album.name ) - + if (appleMusicData?.tracks) { const trackData = appleMusicData.tracks.find( - (t: any) => t.name.toLowerCase() === mostRecentTrack.name.toLowerCase() + (t) => t.name?.toLowerCase() === mostRecentTrack!.name.toLowerCase() ) - + if (trackData?.durationMs) { - isPlaying = this.isTrackPlaying(mostRecentTrack.date, trackData.durationMs) + isPlaying = this.isTrackPlaying(mostRecentTime, trackData.durationMs) if (isPlaying) { playingTrack = mostRecentTrack.name logger.music('debug', `✅ "${playingTrack}" is still playing`)