fix: replace any types in music integration utilities (19 errors)

Phase 1 Batch 5: Music Integration type safety improvements

Fixed 19 any-type errors across 6 music integration files:

1. src/lib/utils/albumEnricher.ts (4 errors)
   - Created RecentTracksData interface
   - Fixed getAppleMusicDataForNowPlaying: return Album['appleMusicData'] | null
   - Fixed cacheRecentTracks: parameter RecentTracksData
   - Fixed getCachedRecentTracks: return RecentTracksData | null
   - Fixed getCachedRecentTracks: data typing

2. src/lib/utils/lastfmStreamManager.ts (4 errors)
   - Created RecentTracksResponse interface
   - Fixed fetchFreshRecentTracks: return RecentTracksResponse
   - Fixed getRecentAlbums: parameter RecentTracksResponse
   - Fixed updateNowPlayingStatus: parameter RecentTracksResponse
   - Fixed getNowPlayingUpdatesForNonRecentAlbums: parameter RecentTracksResponse

3. src/lib/utils/lastfmTransformers.ts (2 errors)
   - Created LastfmTrack interface
   - Fixed trackToAlbum: parameter LastfmTrack
   - Fixed mergeAppleMusicData: parameter Album['appleMusicData']

4. src/lib/utils/nowPlayingDetector.ts (4 errors)
   - Created LastfmRecentTrack and RecentTracksResponse interfaces
   - Fixed processNowPlayingTracks: parameters with proper types
   - Fixed detectNowPlayingAlbums: parameters with proper types
   - Updated appleMusicDataLookup callback: return Album['appleMusicData'] | null

5. src/lib/utils/simpleNowPlayingDetector.ts (3 errors)
   - Created LastfmTrack interface
   - Fixed processAlbums: recentTracks parameter to LastfmTrack[]
   - Fixed appleMusicDataLookup callback: return Album['appleMusicData'] | null
   - Fixed mostRecentTrack variable type and date handling
   - Fixed trackData type in tracks.find()

6. src/lib/utils/simpleLastfmStreamManager.ts (2 errors)
   - Created RecentTracksResponse interface
   - Fixed getRecentAlbums: parameter RecentTracksResponse

Progress: 32 any-type errors remaining (down from 51)
This commit is contained in:
Justin Edmund 2025-11-24 02:25:23 -08:00
parent 799570d979
commit 9f2854bfdc
6 changed files with 165 additions and 34 deletions

View file

@ -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<any> {
async getAppleMusicDataForNowPlaying(
artistName: string,
albumName: string
): Promise<Album['appleMusicData'] | null> {
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<void> {
async cacheRecentTracks(username: string, recentTracks: RecentTracksData): Promise<void> {
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<any | null> {
async getCachedRecentTracks(username: string): Promise<RecentTracksData | null> {
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
}))

View file

@ -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<string, { isPlaying: boolean; track?: string }>
lastAlbumOrder: string[]
@ -78,7 +96,7 @@ export class LastfmStreamManager {
/**
* Fetch fresh recent tracks from Last.fm (no cache)
*/
private async fetchFreshRecentTracks(): Promise<any> {
private async fetchFreshRecentTracks(): Promise<RecentTracksResponse> {
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<Album[]> {
private async getRecentAlbums(
limit: number,
recentTracksResponse?: RecentTracksResponse
): Promise<Album[]> {
// 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<void> {
private async updateNowPlayingStatus(
albums: Album[],
recentTracksResponse?: RecentTracksResponse
): Promise<void> {
// 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<NowPlayingUpdate[]> {
const updates: NowPlayingUpdate[] = []

View file

@ -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
}

View file

@ -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<any>
recentTracksResponse: RecentTracksResponse,
appleMusicDataLookup: (
artistName: string,
albumName: string
) => Promise<Album['appleMusicData'] | null>
): Promise<Map<string, NowPlayingUpdate>> {
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<any>
tracks: LastfmRecentTrack[],
appleMusicDataLookup: (
artistName: string,
albumName: string
) => Promise<Album['appleMusicData'] | null>
): Promise<Map<string, NowPlayingUpdate>> {
const albums: Map<string, NowPlayingUpdate> = new Map()
let hasOfficialNowPlaying = false

View file

@ -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<Album[]> {
private async getRecentAlbums(
limit: number,
recentTracksResponse: RecentTracksResponse
): Promise<Album[]> {
const uniqueAlbums = new Map<string, Album>()
for (const track of recentTracksResponse.tracks) {

View file

@ -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<any>
recentTracks: LastfmTrack[],
appleMusicDataLookup: (
artistName: string,
albumName: string
) => Promise<Album['appleMusicData'] | null>
): Promise<Album[]> {
logger.music('debug', `Processing ${albums.length} albums with ${recentTracks.length} recent tracks`)
@ -61,13 +80,16 @@ export class SimpleNowPlayingDetector {
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,7 +104,7 @@ 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}`
@ -97,11 +119,11 @@ export class SimpleNowPlayingDetector {
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`)