feat: enhance artist page with popular songs and artist bio sections, update Last.fm API integration

This commit is contained in:
2025-07-01 17:45:39 +00:00
committed by GitHub
parent 3ca162e188
commit bc159ac20a
10 changed files with 553 additions and 38 deletions

154
lib/lastfm-api.ts Normal file
View File

@@ -0,0 +1,154 @@
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<any> {
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 || 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 || 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 || 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();

View File

@@ -503,6 +503,26 @@ class NavidromeAPI {
return [];
}
}
async getArtistTopSongs(artistName: string, limit: number = 10): Promise<Song[]> {
try {
// Search for songs by the artist and return them sorted by play count
const searchResult = await this.search2(artistName, 0, 0, limit * 3);
// Filter songs that are actually by this artist (exact match)
const artistSongs = searchResult.songs.filter(song =>
song.artist.toLowerCase() === artistName.toLowerCase()
);
// Sort by play count (descending) and limit results
return artistSongs
.sort((a, b) => (b.playCount || 0) - (a.playCount || 0))
.slice(0, limit);
} catch (error) {
console.error('Failed to get artist top songs:', error);
return [];
}
}
}
// Singleton instance management