@@ -106,7 +106,7 @@ def __init__(self, bot):
106106 def get_auth_url (self , discord_id : int ) -> str :
107107 params = {
108108 "api_key" : LASTFM_API_KEY ,
109- "cb" : f"http ://localhost:5000 /api/lastfm/callback?discord_id={ discord_id } "
109+ "cb" : f"https ://your-production-domain.com /api/lastfm/callback?discord_id={ discord_id } "
110110 }
111111 return f"https://www.last.fm/api/auth/?{ urlencode (params )} "
112112
@@ -267,172 +267,206 @@ async def fm(self, ctx, user: discord.Member = None):
267267 @commands .cooldown (1 , 3 , commands .BucketType .user )
268268 async def fmtopartists (self , ctx , user : discord .Member = None , limit : int = 5 ):
269269 """Show top artists."""
270- username , session_key , display_name , _ = await self .get_user_and_session (ctx , user )
271- if not username or not session_key :
272- return await ctx .reply (f"{ display_name } has not linked their Last.fm account. Use `.fmlink` to link." )
273- data = await self .get_lastfm_data ("user.gettopartists" , username , session_key , limit = limit )
274- artists = data .get ("topartists" , {}).get ("artist" , [])
275- if not artists :
276- return await ctx .reply ("No top artists found." )
277- msg = "\n " .join ([f"**{ i + 1 } .** [{ a ['name' ]} ](https://last.fm/music/{ a ['name' ].replace (' ' , '+' )} ) (`{ a ['playcount' ]} plays`)" for i , a in enumerate (artists )])
278- embed = create_embed (
279- f"Top { limit } artists for { username } " ,
280- msg ,
281- color = discord .Color .purple (),
282- footer = (f"Requested by { ctx .author } " , ctx .author .display_avatar .url )
283- )
284- await ctx .reply (embed = embed )
270+ try :
271+ username , session_key , display_name , _ = await self .get_user_and_session (ctx , user )
272+ if not username or not session_key :
273+ return await ctx .reply (f"{ display_name } has not linked their Last.fm account. Use `.fmlink` to link." )
274+ data = await self .get_lastfm_data ("user.gettopartists" , username , session_key , limit = limit )
275+ if "error" in data :
276+ return await ctx .reply (f"❌ Last.fm error: { data ['error' ]} " )
277+ artists = data .get ("topartists" , {}).get ("artist" , [])
278+ if not artists :
279+ return await ctx .reply ("No top artists found." )
280+ msg = "\n " .join ([f"**{ i + 1 } .** [{ a ['name' ]} ](https://last.fm/music/{ a ['name' ].replace (' ' , '+' )} ) (`{ a ['playcount' ]} plays`)" for i , a in enumerate (artists )])
281+ embed = create_embed (
282+ f"Top { limit } artists for { username } " ,
283+ msg ,
284+ color = discord .Color .purple (),
285+ footer = (f"Requested by { ctx .author } " , ctx .author .display_avatar .url )
286+ )
287+ await ctx .reply (embed = embed )
288+ except Exception as e :
289+ await ctx .reply (f"❌ Unexpected error: { e } " )
285290
286291 @commands .command (aliases = ["tracks" ])
287292 @commands .cooldown (1 , 3 , commands .BucketType .user )
288293 async def fmtoptracks (self , ctx , user : discord .Member = None , limit : int = 5 ):
289294 """Show top tracks."""
290- username , session_key , display_name , _ = await self .get_user_and_session (ctx , user )
291- if not username or not session_key :
292- return await ctx .reply (f"{ display_name } has not linked their Last.fm account. Use `.fmlink` to link." )
293- data = await self .get_lastfm_data ("user.gettoptracks" , username , session_key , limit = limit )
294- tracks = data .get ("toptracks" , {}).get ("track" , [])
295- if not tracks :
296- return await ctx .reply ("No top tracks found." )
297- msg = "\n " .join ([f"**{ i + 1 } .** [{ t ['name' ]} ](https://last.fm/music/{ t ['artist' ]['name' ].replace (' ' , '+' )} /_/{ t ['name' ].replace (' ' , '+' )} ) by { t ['artist' ]['name' ]} (`{ t ['playcount' ]} plays`)" for i , t in enumerate (tracks )])
298- embed = create_embed (
299- f"Top { limit } tracks for { username } " ,
300- msg ,
301- color = discord .Color .purple (),
302- footer = (f"Requested by { ctx .author } " , ctx .author .display_avatar .url )
303- )
304- await ctx .reply (embed = embed )
295+ try :
296+ username , session_key , display_name , _ = await self .get_user_and_session (ctx , user )
297+ if not username or not session_key :
298+ return await ctx .reply (f"{ display_name } has not linked their Last.fm account. Use `.fmlink` to link." )
299+ data = await self .get_lastfm_data ("user.gettoptracks" , username , session_key , limit = limit )
300+ if "error" in data :
301+ return await ctx .reply (f"❌ Last.fm error: { data ['error' ]} " )
302+ tracks = data .get ("toptracks" , {}).get ("track" , [])
303+ if not tracks :
304+ return await ctx .reply ("No top tracks found." )
305+ msg = "\n " .join ([f"**{ i + 1 } .** [{ t ['name' ]} ](https://last.fm/music/{ t ['artist' ]['name' ].replace (' ' , '+' )} /_/{ t ['name' ].replace (' ' , '+' )} ) by { t ['artist' ]['name' ]} (`{ t ['playcount' ]} plays`)" for i , t in enumerate (tracks )])
306+ embed = create_embed (
307+ f"Top { limit } tracks for { username } " ,
308+ msg ,
309+ color = discord .Color .purple (),
310+ footer = (f"Requested by { ctx .author } " , ctx .author .display_avatar .url )
311+ )
312+ await ctx .reply (embed = embed )
313+ except Exception as e :
314+ await ctx .reply (f"❌ Unexpected error: { e } " )
305315
306316 @commands .command (aliases = ["album" ])
307317 @commands .cooldown (1 , 3 , commands .BucketType .user )
308318 async def fmalbum (self , ctx , * , album : str ):
309319 """Show album info."""
310- if not LASTFM_API_KEY :
311- return await ctx .reply ("❌ Last.fm API key not set." )
312- if " - " in album :
313- artist , album_name = album .split (" - " , 1 )
314- else :
315- return await ctx .reply ("Please use the format: `.fmalbum artist - album`" )
316- params = {"artist" : artist .strip (), "album" : album_name .strip ()}
317- data = await self .get_lastfm_data ("album.getinfo" , ** params )
318- albuminfo = data .get ("album" )
319- if not albuminfo :
320- return await ctx .reply ("Album not found." )
321- embed = create_embed (
322- albuminfo .get ('name' , 'Unknown Album' ),
323- f"**Artist:** { albuminfo .get ('artist' , 'N/A' )} \n "
324- f"**Listeners:** `{ albuminfo .get ('listeners' , 'N/A' )} `\n "
325- f"**Playcount:** `{ albuminfo .get ('playcount' , 'N/A' )} `" ,
326- color = discord .Color .gold (),
327- url = albuminfo .get ("url" , "" ),
328- thumbnail = albuminfo ["image" ][- 1 ]["#text" ] if albuminfo .get ("image" ) else None
329- )
330- await ctx .reply (embed = embed )
320+ try :
321+ if not LASTFM_API_KEY :
322+ return await ctx .reply ("❌ Last.fm API key not set." )
323+ if " - " in album :
324+ artist , album_name = album .split (" - " , 1 )
325+ else :
326+ return await ctx .reply ("Please use the format: `.fmalbum artist - album`" )
327+ params = {"artist" : artist .strip (), "album" : album_name .strip ()}
328+ data = await self .get_lastfm_data ("album.getinfo" , ** params )
329+ if "error" in data :
330+ return await ctx .reply (f"❌ Last.fm error: { data ['error' ]} " )
331+ albuminfo = data .get ("album" )
332+ if not albuminfo :
333+ return await ctx .reply ("Album not found." )
334+ embed = create_embed (
335+ albuminfo .get ('name' , 'Unknown Album' ),
336+ f"**Artist:** { albuminfo .get ('artist' , 'N/A' )} \n "
337+ f"**Listeners:** `{ albuminfo .get ('listeners' , 'N/A' )} `\n "
338+ f"**Playcount:** `{ albuminfo .get ('playcount' , 'N/A' )} `" ,
339+ color = discord .Color .gold (),
340+ url = albuminfo .get ("url" , "" ),
341+ thumbnail = albuminfo ["image" ][- 1 ]["#text" ] if albuminfo .get ("image" ) else None
342+ )
343+ await ctx .reply (embed = embed )
344+ except Exception as e :
345+ await ctx .reply (f"❌ Unexpected error: { e } " )
331346
332347 @commands .command (aliases = ["artist" ])
333348 @commands .cooldown (1 , 3 , commands .BucketType .user )
334349 async def fmartist (self , ctx , * , artist : str ):
335350 """Show artist info."""
336- if not LASTFM_API_KEY :
337- return await ctx .reply ("❌ Last.fm API key not set." )
338- data = await self .get_lastfm_data ("artist.getinfo" , artist = artist )
339- artistinfo = data .get ("artist" )
340- if not artistinfo :
341- return await ctx .reply ("Artist not found." )
342- bio = artistinfo .get ("bio" , {}).get ("summary" , "" )
343- embed = create_embed (
344- artistinfo .get ('name' , 'Unknown Artist' ),
345- f"**Listeners:** `{ artistinfo .get ('stats' , {}).get ('listeners' , 'N/A' )} `\n "
346- f"**Playcount:** `{ artistinfo .get ('stats' , {}).get ('playcount' , 'N/A' )} `\n \n "
347- f"{ bio [:500 ]} { '...' if len (bio ) > 500 else '' } " ,
348- color = discord .Color .gold (),
349- url = artistinfo .get ("url" , "" ),
350- thumbnail = artistinfo ["image" ][- 1 ]["#text" ] if artistinfo .get ("image" ) else None
351- )
352- await ctx .reply (embed = embed )
351+ try :
352+ if not LASTFM_API_KEY :
353+ return await ctx .reply ("❌ Last.fm API key not set." )
354+ data = await self .get_lastfm_data ("artist.getinfo" , artist = artist )
355+ if "error" in data :
356+ return await ctx .reply (f"❌ Last.fm error: { data ['error' ]} " )
357+ artistinfo = data .get ("artist" )
358+ if not artistinfo :
359+ return await ctx .reply ("Artist not found." )
360+ bio = artistinfo .get ("bio" , {}).get ("summary" , "" )
361+ embed = create_embed (
362+ artistinfo .get ('name' , 'Unknown Artist' ),
363+ f"**Listeners:** `{ artistinfo .get ('stats' , {}).get ('listeners' , 'N/A' )} `\n "
364+ f"**Playcount:** `{ artistinfo .get ('stats' , {}).get ('playcount' , 'N/A' )} `\n \n "
365+ f"{ bio [:500 ]} { '...' if len (bio ) > 500 else '' } " ,
366+ color = discord .Color .gold (),
367+ url = artistinfo .get ("url" , "" ),
368+ thumbnail = artistinfo ["image" ][- 1 ]["#text" ] if artistinfo .get ("image" ) else None
369+ )
370+ await ctx .reply (embed = embed )
371+ except Exception as e :
372+ await ctx .reply (f"❌ Unexpected error: { e } " )
353373
354374 @commands .command (aliases = ["recent" ])
355375 @commands .cooldown (1 , 3 , commands .BucketType .user )
356376 async def fmrecent (self , ctx , user : discord .Member = None , count : int = 5 ):
357377 """Show recent tracks."""
358- username , session_key , display_name , _ = await self .get_user_and_session (ctx , user )
359- if not username or not session_key :
360- return await ctx .reply (f"{ display_name } has not linked their Last.fm account. Use `.fmlink` to link." )
361- data = await self .get_lastfm_data ("user.getrecenttracks" , username , session_key , limit = count )
362- tracks = data .get ("recenttracks" , {}).get ("track" , [])
363- if not tracks :
364- return await ctx .reply ("No recent tracks found." )
365- msg = "\n " .join ([f"**{ i + 1 } .** [{ t ['name' ]} ](https://last.fm/music/{ t ['artist' ]['#text' ].replace (' ' , '+' )} /_/{ t ['name' ].replace (' ' , '+' )} ) by { t ['artist' ]['#text' ]} " for i , t in enumerate (tracks )])
366- embed = create_embed (
367- f"Last { count } tracks for { username } " ,
368- msg ,
369- color = discord .Color .blurple ()
370- )
371- await ctx .reply (embed = embed )
372-
373- @commands .command (aliases = ["servertop" ])
374- @commands .cooldown (1 , 10 , commands .BucketType .guild )
375- async def fmservertop (self , ctx , limit : int = 5 ):
376- """Show server's most played artists."""
377- if ctx .guild is None :
378- return await ctx .reply ("This command can only be used in a server." )
379- usernames = [self .get_linked_user (m .id )[0 ] for m in ctx .guild .members if self .get_linked_user (m .id )[0 ]]
380- if not usernames :
381- return await ctx .reply ("No users in this server have linked their Last.fm accounts." )
382-
383- async def fetch_artists (username ):
384- await asyncio .sleep (0.1 ) # small delay to avoid rate limits
385- data = await self .get_lastfm_data ("user.gettopartists" , username , limit = limit )
386- return data .get ("topartists" , {}).get ("artist" , [])
387-
388- async with ctx .typing ():
389- # Fetch all users' top artists concurrently
390- all_artists_lists = await asyncio .gather (* (fetch_artists (u ) for u in usernames ))
391- artist_counter = Counter ()
392- for artists in all_artists_lists :
393- for a in artists :
394- artist_counter [a ["name" ]] += int (a .get ("playcount" , 0 ))
395- if not artist_counter :
396- return await ctx .reply ("No artist data found for this server." )
397- top = artist_counter .most_common (limit )
398- msg = "\n " .join ([f"**{ i + 1 } .** { name } (`{ plays } plays`)" for i , (name , plays ) in enumerate (top )])
378+ try :
379+ username , session_key , display_name , _ = await self .get_user_and_session (ctx , user )
380+ if not username or not session_key :
381+ return await ctx .reply (f"{ display_name } has not linked their Last.fm account. Use `.fmlink` to link." )
382+ data = await self .get_lastfm_data ("user.getrecenttracks" , username , session_key , limit = count )
383+ if "error" in data :
384+ return await ctx .reply (f"❌ Last.fm error: { data ['error' ]} " )
385+ tracks = data .get ("recenttracks" , {}).get ("track" , [])
386+ if not tracks :
387+ return await ctx .reply ("No recent tracks found." )
388+ msg = "\n " .join ([f"**{ i + 1 } .** [{ t ['name' ]} ](https://last.fm/music/{ t ['artist' ]['#text' ].replace (' ' , '+' )} /_/{ t ['name' ].replace (' ' , '+' )} ) by { t ['artist' ]['#text' ]} " for i , t in enumerate (tracks )])
399389 embed = create_embed (
400- f"Server Top { limit } Artists " ,
390+ f"Last { count } tracks for { username } " ,
401391 msg ,
402392 color = discord .Color .blurple ()
403393 )
404394 await ctx .reply (embed = embed )
395+ except Exception as e :
396+ await ctx .reply (f"❌ Unexpected error: { e } " )
397+
398+ @commands .command (aliases = ["servertop" ])
399+ @commands .cooldown (1 , 10 , commands .BucketType .guild )
400+ async def fmservertop (self , ctx , limit : int = 5 ):
401+ """Show server's most played artists."""
402+ try :
403+ if ctx .guild is None :
404+ return await ctx .reply ("This command can only be used in a server." )
405+ usernames = [self .get_linked_user (m .id )[0 ] for m in ctx .guild .members if self .get_linked_user (m .id )[0 ]]
406+ if not usernames :
407+ return await ctx .reply ("No users in this server have linked their Last.fm accounts." )
408+
409+ async def fetch_artists (username ):
410+ await asyncio .sleep (0.1 ) # small delay to avoid rate limits
411+ data = await self .get_lastfm_data ("user.gettopartists" , username , limit = limit )
412+ if "error" in data :
413+ return []
414+ return data .get ("topartists" , {}).get ("artist" , [])
415+
416+ async with ctx .typing ():
417+ all_artists_lists = await asyncio .gather (* (fetch_artists (u ) for u in usernames ))
418+ artist_counter = Counter ()
419+ for artists in all_artists_lists :
420+ for a in artists :
421+ artist_counter [a ["name" ]] += int (a .get ("playcount" , 0 ))
422+ if not artist_counter :
423+ return await ctx .reply ("No artist data found for this server." )
424+ top = artist_counter .most_common (limit )
425+ msg = "\n " .join ([f"**{ i + 1 } .** { name } (`{ plays } plays`)" for i , (name , plays ) in enumerate (top )])
426+ embed = create_embed (
427+ f"Server Top { limit } Artists" ,
428+ msg ,
429+ color = discord .Color .blurple ()
430+ )
431+ await ctx .reply (embed = embed )
432+ except Exception as e :
433+ await ctx .reply (f"❌ Unexpected error: { e } " )
405434
406435 @commands .command (aliases = ["topscrobblers" ])
407436 @commands .cooldown (1 , 10 , commands .BucketType .guild )
408437 async def fmleaderboard (self , ctx ):
409438 """Show top scrobblers in the server."""
410- if ctx .guild is None :
411- return await ctx .reply ("This command can only be used in a server." )
412- members = [m for m in ctx .guild .members if self .get_linked_user (m .id )[0 ]]
413- if not members :
414- return await ctx .reply ("No users in this server have linked their Last.fm accounts." )
415-
416- async def fetch_playcount (member ):
417- await asyncio .sleep (0.1 ) # small delay to avoid rate limits
418- username , _ = self .get_linked_user (member .id )
419- data = await self .get_lastfm_data ("user.getinfo" , username )
420- playcount = int (data .get ("user" , {}).get ("playcount" , 0 ))
421- return (member .display_name , playcount )
422-
423- async with ctx .typing ():
424- leaderboard = await asyncio .gather (* (fetch_playcount (m ) for m in members ))
425- leaderboard = [entry for entry in leaderboard if entry [1 ] > 0 ]
426- if not leaderboard :
439+ try :
440+ if ctx .guild is None :
441+ return await ctx .reply ("This command can only be used in a server." )
442+ members = [m for m in ctx .guild .members if self .get_linked_user (m .id )[0 ]]
443+ if not members :
427444 return await ctx .reply ("No users in this server have linked their Last.fm accounts." )
428- leaderboard .sort (key = lambda x : x [1 ], reverse = True )
429- msg = "\n " .join ([f"**{ i + 1 } .** { name } (`{ plays } plays`)" for i , (name , plays ) in enumerate (leaderboard [:10 ])])
430- embed = create_embed (
431- "Top Scrobblers in This Server" ,
432- msg ,
433- color = discord .Color .blurple ()
434- )
435- await ctx .reply (embed = embed )
445+
446+ async def fetch_playcount (member ):
447+ await asyncio .sleep (0.1 ) # small delay to avoid rate limits
448+ username , _ = self .get_linked_user (member .id )
449+ data = await self .get_lastfm_data ("user.getinfo" , username )
450+ if "error" in data :
451+ return (member .display_name , 0 )
452+ playcount = int (data .get ("user" , {}).get ("playcount" , 0 ))
453+ return (member .display_name , playcount )
454+
455+ async with ctx .typing ():
456+ leaderboard = await asyncio .gather (* (fetch_playcount (m ) for m in members ))
457+ leaderboard = [entry for entry in leaderboard if entry [1 ] > 0 ]
458+ if not leaderboard :
459+ return await ctx .reply ("No users in this server have linked their Last.fm accounts." )
460+ leaderboard .sort (key = lambda x : x [1 ], reverse = True )
461+ msg = "\n " .join ([f"**{ i + 1 } .** { name } (`{ plays } plays`)" for i , (name , plays ) in enumerate (leaderboard [:10 ])])
462+ embed = create_embed (
463+ "Top Scrobblers in This Server" ,
464+ msg ,
465+ color = discord .Color .blurple ()
466+ )
467+ await ctx .reply (embed = embed )
468+ except Exception as e :
469+ await ctx .reply (f"❌ Unexpected error: { e } " )
436470
437471 @commands .command ()
438472 @commands .has_permissions (administrator = True )
0 commit comments