155 lines
3.6 KiB
TypeScript
155 lines
3.6 KiB
TypeScript
interface LastFmCredentials {
|
|
apiKey: string;
|
|
apiSecret: string;
|
|
sessionKey?: string;
|
|
username?: string;
|
|
}
|
|
|
|
interface LastFmArtistInfo {
|
|
name: string;
|
|
bio?: {
|
|
summary: string;
|
|
content: string;
|
|
};
|
|
stats?: {
|
|
listeners: string;
|
|
playcount: string;
|
|
};
|
|
similar?: {
|
|
artist: Array<{
|
|
name: string;
|
|
url: string;
|
|
image: Array<{
|
|
'#text': string;
|
|
size: string;
|
|
}>;
|
|
}>;
|
|
};
|
|
tags?: {
|
|
tag: Array<{
|
|
name: string;
|
|
url: string;
|
|
}>;
|
|
};
|
|
image?: Array<{
|
|
'#text': string;
|
|
size: string;
|
|
}>;
|
|
}
|
|
|
|
interface LastFmTopTracks {
|
|
track: Array<{
|
|
name: string;
|
|
playcount: string;
|
|
listeners: string;
|
|
artist: {
|
|
name: string;
|
|
mbid: string;
|
|
url: string;
|
|
};
|
|
image: Array<{
|
|
'#text': string;
|
|
size: string;
|
|
}>;
|
|
'@attr': {
|
|
rank: string;
|
|
};
|
|
}>;
|
|
}
|
|
|
|
export class LastFmAPI {
|
|
private baseUrl = 'https://ws.audioscrobbler.com/2.0/';
|
|
|
|
private getCredentials(): LastFmCredentials | null {
|
|
if (typeof window === 'undefined') return null;
|
|
|
|
const stored = localStorage.getItem('lastfm-credentials');
|
|
if (!stored) return null;
|
|
|
|
try {
|
|
return JSON.parse(stored);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private async makeRequest(method: string, params: Record<string, string>): Promise<Record<string, unknown>> {
|
|
const credentials = this.getCredentials();
|
|
if (!credentials?.apiKey) {
|
|
throw new Error('No Last.fm API key available');
|
|
}
|
|
|
|
const url = new URL(this.baseUrl);
|
|
url.searchParams.append('method', method);
|
|
url.searchParams.append('api_key', credentials.apiKey);
|
|
url.searchParams.append('format', 'json');
|
|
|
|
Object.entries(params).forEach(([key, value]) => {
|
|
url.searchParams.append(key, value);
|
|
});
|
|
|
|
const response = await fetch(url.toString());
|
|
if (!response.ok) {
|
|
throw new Error(`Last.fm API error: ${response.statusText}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
if (data.error) {
|
|
throw new Error(data.message || 'Last.fm API error');
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
async getArtistInfo(artistName: string): Promise<LastFmArtistInfo | null> {
|
|
try {
|
|
const data = await this.makeRequest('artist.getInfo', {
|
|
artist: artistName,
|
|
autocorrect: '1'
|
|
});
|
|
|
|
return (data.artist as LastFmArtistInfo) || null;
|
|
} catch (error) {
|
|
console.error('Failed to fetch artist info from Last.fm:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async getArtistTopTracks(artistName: string, limit: number = 10): Promise<LastFmTopTracks | null> {
|
|
try {
|
|
const data = await this.makeRequest('artist.getTopTracks', {
|
|
artist: artistName,
|
|
limit: limit.toString(),
|
|
autocorrect: '1'
|
|
});
|
|
|
|
return (data.toptracks as LastFmTopTracks) || null;
|
|
} catch (error) {
|
|
console.error('Failed to fetch artist top tracks from Last.fm:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async getSimilarArtists(artistName: string, limit: number = 6): Promise<LastFmArtistInfo['similar'] | null> {
|
|
try {
|
|
const data = await this.makeRequest('artist.getSimilar', {
|
|
artist: artistName,
|
|
limit: limit.toString(),
|
|
autocorrect: '1'
|
|
});
|
|
|
|
return (data.similarartists as LastFmArtistInfo['similar']) || null;
|
|
} catch (error) {
|
|
console.error('Failed to fetch similar artists from Last.fm:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
isAvailable(): boolean {
|
|
const credentials = this.getCredentials();
|
|
return !!credentials?.apiKey;
|
|
}
|
|
}
|
|
|
|
export const lastFmAPI = new LastFmAPI();
|