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 redis from '../../routes/api/redis-client'
import { logger } from '$lib/server/logger' 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 { export class AlbumEnricher {
private client: LastClient private client: LastClient
private cacheTTL = { private cacheTTL = {
@ -116,7 +128,10 @@ export class AlbumEnricher {
/** /**
* Get Apple Music data for duration-based now playing detection * 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 cacheKey = `apple:album:${artistName}:${albumName}`
const cached = await redis.get(cacheKey) const cached = await redis.get(cacheKey)
@ -146,7 +161,7 @@ export class AlbumEnricher {
/** /**
* Cache recent tracks from Last.fm * 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}` const cacheKey = `lastfm:recent:${username}`
await redis.set(cacheKey, JSON.stringify(recentTracks), 'EX', this.cacheTTL.recentTracks) await redis.set(cacheKey, JSON.stringify(recentTracks), 'EX', this.cacheTTL.recentTracks)
} }
@ -154,15 +169,15 @@ export class AlbumEnricher {
/** /**
* Get cached recent tracks * Get cached recent tracks
*/ */
async getCachedRecentTracks(username: string): Promise<any | null> { async getCachedRecentTracks(username: string): Promise<RecentTracksData | null> {
const cacheKey = `lastfm:recent:${username}` const cacheKey = `lastfm:recent:${username}`
const cached = await redis.get(cacheKey) const cached = await redis.get(cacheKey)
if (cached) { if (cached) {
const data = JSON.parse(cached) const data = JSON.parse(cached) as RecentTracksData
// Convert date strings back to Date objects // Convert date strings back to Date objects
if (data.tracks) { if (data.tracks) {
data.tracks = data.tracks.map((track: any) => ({ data.tracks = data.tracks.map((track) => ({
...track, ...track,
date: track.date ? new Date(track.date) : undefined date: track.date ? new Date(track.date) : undefined
})) }))

View file

@ -5,6 +5,24 @@ import { AlbumEnricher } from './albumEnricher'
import { trackToAlbum, getAlbumKey } from './lastfmTransformers' import { trackToAlbum, getAlbumKey } from './lastfmTransformers'
import { logger } from '$lib/server/logger' 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 { export interface StreamState {
lastNowPlayingState: Map<string, { isPlaying: boolean; track?: string }> lastNowPlayingState: Map<string, { isPlaying: boolean; track?: string }>
lastAlbumOrder: string[] lastAlbumOrder: string[]
@ -78,7 +96,7 @@ export class LastfmStreamManager {
/** /**
* Fetch fresh recent tracks from Last.fm (no cache) * 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') logger.music('debug', 'Fetching fresh Last.fm recent tracks for now playing detection')
const recentTracksResponse = await this.client.user.getRecentTracks(this.username, { const recentTracksResponse = await this.client.user.getRecentTracks(this.username, {
limit: 50, limit: 50,
@ -92,7 +110,10 @@ export class LastfmStreamManager {
/** /**
* Get recent albums from Last.fm * 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 // Use provided fresh data or fetch new
if (!recentTracksResponse) { if (!recentTracksResponse) {
recentTracksResponse = await this.fetchFreshRecentTracks() recentTracksResponse = await this.fetchFreshRecentTracks()
@ -124,7 +145,10 @@ export class LastfmStreamManager {
/** /**
* Update now playing status using the detector * 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 // Use provided fresh data or fetch new
if (!recentTracksResponse) { if (!recentTracksResponse) {
recentTracksResponse = await this.fetchFreshRecentTracks() recentTracksResponse = await this.fetchFreshRecentTracks()
@ -237,7 +261,7 @@ export class LastfmStreamManager {
*/ */
private async getNowPlayingUpdatesForNonRecentAlbums( private async getNowPlayingUpdatesForNonRecentAlbums(
recentAlbums: Album[], recentAlbums: Album[],
recentTracksResponse?: any recentTracksResponse?: RecentTracksResponse
): Promise<NowPlayingUpdate[]> { ): Promise<NowPlayingUpdate[]> {
const updates: NowPlayingUpdate[] = [] const updates: NowPlayingUpdate[] = []

View file

@ -1,6 +1,25 @@
import type { Album, AlbumImages } from '$lib/types/lastfm' import type { Album, AlbumImages } from '$lib/types/lastfm'
import type { LastfmImage } from '@musicorum/lastfm/dist/types/packages/common' 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 * 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 * Transform track data into an Album object
*/ */
export function trackToAlbum(track: any, rank: number): Album { export function trackToAlbum(track: LastfmTrack, rank: number): Album {
return { return {
name: track.album.name, name: track.album.name,
artist: { artist: {
@ -53,7 +72,7 @@ export function trackToAlbum(track: any, rank: number): Album {
playCount: 1, playCount: 1,
images: transformImages(track.images), images: transformImages(track.images),
mbid: track.album.mbid || '', mbid: track.album.mbid || '',
url: track.url, url: track.url || '',
rank, rank,
isNowPlaying: track.nowPlaying || false, isNowPlaying: track.nowPlaying || false,
nowPlayingTrack: track.nowPlaying ? track.name : undefined, 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 * 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 { return {
...album, ...album,
images: { images: {
...album.images, ...album.images,
itunes: appleMusicData.highResArtwork || album.images.itunes itunes: appleMusicData?.highResArtwork || album.images.itunes
}, },
appleMusicData appleMusicData
} }

View file

@ -1,4 +1,5 @@
import { logger } from '$lib/server/logger' import { logger } from '$lib/server/logger'
import type { Album } from '$lib/types/lastfm'
export interface TrackPlayInfo { export interface TrackPlayInfo {
albumName: string albumName: string
@ -19,6 +20,29 @@ export interface NowPlayingResult {
nowPlayingTrack?: string 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 // Configuration constants
const SCROBBLE_LAG = 3 * 60 * 1000 // 3 minutes to account for Last.fm scrobble delay 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 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 * Process now playing data from Last.fm API response
*/ */
processNowPlayingTracks( processNowPlayingTracks(
recentTracksResponse: any, recentTracksResponse: RecentTracksResponse,
appleMusicDataLookup: (artistName: string, albumName: string) => Promise<any> appleMusicDataLookup: (
artistName: string,
albumName: string
) => Promise<Album['appleMusicData'] | null>
): Promise<Map<string, NowPlayingUpdate>> { ): Promise<Map<string, NowPlayingUpdate>> {
return this.detectNowPlayingAlbums(recentTracksResponse.tracks, appleMusicDataLookup) return this.detectNowPlayingAlbums(recentTracksResponse.tracks, appleMusicDataLookup)
} }
@ -149,8 +176,11 @@ export class NowPlayingDetector {
* Detect which albums are currently playing from a list of tracks * Detect which albums are currently playing from a list of tracks
*/ */
private async detectNowPlayingAlbums( private async detectNowPlayingAlbums(
tracks: any[], tracks: LastfmRecentTrack[],
appleMusicDataLookup: (artistName: string, albumName: string) => Promise<any> appleMusicDataLookup: (
artistName: string,
albumName: string
) => Promise<Album['appleMusicData'] | null>
): Promise<Map<string, NowPlayingUpdate>> { ): Promise<Map<string, NowPlayingUpdate>> {
const albums: Map<string, NowPlayingUpdate> = new Map() const albums: Map<string, NowPlayingUpdate> = new Map()
let hasOfficialNowPlaying = false let hasOfficialNowPlaying = false

View file

@ -5,6 +5,24 @@ import { AlbumEnricher } from './albumEnricher'
import { trackToAlbum } from './lastfmTransformers' import { trackToAlbum } from './lastfmTransformers'
import { logger } from '$lib/server/logger' 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 { export interface StreamUpdate {
albums?: Album[] albums?: Album[]
} }
@ -90,7 +108,10 @@ export class SimpleLastfmStreamManager {
/** /**
* Get recent albums from Last.fm tracks * 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>() const uniqueAlbums = new Map<string, Album>()
for (const track of recentTracksResponse.tracks) { for (const track of recentTracksResponse.tracks) {

View file

@ -1,6 +1,22 @@
import type { Album } from '$lib/types/lastfm' import type { Album } from '$lib/types/lastfm'
import { logger } from '$lib/server/logger' 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 // Simple buffer time for tracks that might have paused/buffered
const BUFFER_TIME_MS = 30000 // 30 seconds grace period const BUFFER_TIME_MS = 30000 // 30 seconds grace period
@ -27,8 +43,11 @@ export class SimpleNowPlayingDetector {
*/ */
async processAlbums( async processAlbums(
albums: Album[], albums: Album[],
recentTracks: any[], recentTracks: LastfmTrack[],
appleMusicDataLookup: (artistName: string, albumName: string) => Promise<any> appleMusicDataLookup: (
artistName: string,
albumName: string
) => Promise<Album['appleMusicData'] | null>
): Promise<Album[]> { ): Promise<Album[]> {
logger.music('debug', `Processing ${albums.length} albums with ${recentTracks.length} recent tracks`) logger.music('debug', `Processing ${albums.length} albums with ${recentTracks.length} recent tracks`)
@ -61,15 +80,18 @@ export class SimpleNowPlayingDetector {
logger.music('debug', 'Using duration-based detection') logger.music('debug', 'Using duration-based detection')
// Find the most recent track across all albums // Find the most recent track across all albums
let mostRecentTrack: any = null let mostRecentTrack: LastfmTrack | null = null
let mostRecentTime = new Date(0) let mostRecentTime = new Date(0)
for (const track of recentTracks) { for (const track of recentTracks) {
if (track.date && track.date > mostRecentTime) { if (track.date && typeof track.date === 'object' && 'uts' in track.date) {
mostRecentTime = track.date const trackTime = new Date(Number(track.date.uts) * 1000)
if (trackTime > mostRecentTime) {
mostRecentTime = trackTime
mostRecentTrack = track mostRecentTrack = track
} }
} }
}
if (!mostRecentTrack) { if (!mostRecentTrack) {
// No recent tracks, nothing is playing // No recent tracks, nothing is playing
@ -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', `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 // Check if the most recent track is still playing
const albumKey = `${mostRecentTrack.artist.name}:${mostRecentTrack.album.name}` const albumKey = `${mostRecentTrack.artist.name}:${mostRecentTrack.album.name}`
@ -97,11 +119,11 @@ export class SimpleNowPlayingDetector {
if (appleMusicData?.tracks) { if (appleMusicData?.tracks) {
const trackData = appleMusicData.tracks.find( const trackData = appleMusicData.tracks.find(
(t: any) => t.name.toLowerCase() === mostRecentTrack.name.toLowerCase() (t) => t.name?.toLowerCase() === mostRecentTrack!.name.toLowerCase()
) )
if (trackData?.durationMs) { if (trackData?.durationMs) {
isPlaying = this.isTrackPlaying(mostRecentTrack.date, trackData.durationMs) isPlaying = this.isTrackPlaying(mostRecentTime, trackData.durationMs)
if (isPlaying) { if (isPlaying) {
playingTrack = mostRecentTrack.name playingTrack = mostRecentTrack.name
logger.music('debug', `✅ "${playingTrack}" is still playing`) logger.music('debug', `✅ "${playingTrack}" is still playing`)