Skip to content

Commit fa4f382

Browse files
mhm
1 parent 63f212b commit fa4f382

3 files changed

Lines changed: 232 additions & 138 deletions

File tree

cogs/LastFm.py

Lines changed: 170 additions & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -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)

dashboard/api/index.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,17 @@
55
# Add the root directory to Python path
66
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
77

8-
from app import app as flask_app
8+
# Import the blueprint
9+
from lastfm_callback import lastfm_callback
10+
11+
app = Flask(__name__)
12+
13+
# Register the blueprint
14+
app.register_blueprint(lastfm_callback)
915

1016
def handler(request: Request):
1117
"""Handle requests in Vercel serverless function"""
12-
return flask_app.wsgi_app(request.environ, lambda x, y: y)
18+
return app.wsgi_app(request.environ, lambda x, y: y)
19+
20+
if __name__ == "__main__":
21+
app.run(port=5000)

0 commit comments

Comments
 (0)