Compare commits

...

13 commits

Author SHA1 Message Date
c3bba4bb20 separate album and track match cases 2025-10-09 04:10:18 -03:00
3a18e9d543 fix mixing enumerate function 2025-10-09 04:06:58 -03:00
06f358c7a5 fix extra cacheable argument 2025-10-09 04:04:45 -03:00
7b214d87c9 add top songs command 2025-10-09 04:01:27 -03:00
1aa5a9b36c add top albums command 2025-10-09 04:01:18 -03:00
f45d91d0c0 create top artists command 2025-10-09 03:54:33 -03:00
55d2409a80 create top list builder 2025-10-09 03:51:56 -03:00
ec25b73c4f add mention checker to utils 2025-10-09 02:35:01 -03:00
b32a704e12 add notice 2025-10-09 01:30:00 -03:00
c9c8bff683 fix collage method 2025-10-09 01:23:53 -03:00
a889b78efb move collage method inside setup 2025-10-09 01:21:58 -03:00
6b7e38cd0f add username finder function 2025-10-09 01:07:37 -03:00
0d7271eee4 correct collage exception 2025-10-09 00:25:02 -03:00
7 changed files with 207 additions and 105 deletions

View file

@ -6,13 +6,14 @@ dead simple last.fm bot
[click here to invite!!](https://nerimity.com/bot/1693424011649925120?perms=2)
(i cannot promise stable uptime)
**note:** i am not planning on implementing leaderbords (`.fmwk`) due to the strain it would put on my server.
**TODO:**
- add database username checker to utils.py
- ~~add database username checker to utils.py~~ ✅ (added to bot.py)
- ~~add collage generation~~
- fix attachment generation and upload
- ~~**temporary fix**: upload and link to catbox urls~~ __the asynchronous catbox library does not accept byte arrays__
- ~~**temporary fix**: construct the attachment object from scratch and upload manually(?)~~ __its still broken__
- ~~**temporary fix:** use 0x0.st~~
- **permanent fix**: fork and update nerimity library
- add list generation

24
bot.py
View file

@ -10,6 +10,8 @@ from pyzxz.pyzxz.pyzxz import ZeroXZero # :sob:
from lastfmcollagegenerator.collage_generator import CollageGenerator
# from catbox_async_uploader.catbox_async_uploader.core import CatboxAsyncUploader
import utils as u
class Bot(nerimity.Client):
"""Extended client class for extra functionality."""
def __init__(self, lastfm_api_key: str, lastfm_api_secret: str,
@ -90,3 +92,25 @@ class Bot(nerimity.Client):
) as cursor:
row = await cursor.fetchone()
return row[0] if row else None
async def find_lastfm_username(self, ctx: nerimity.Context, username: str = None):
if not username:
try: username = await self.get_lastfm(ctx.author.id)
except Exception as e:
print(e)
await ctx.send(u.error_msg(f"Unknown database error:\n{e}"))
if not username:
await ctx.send(u.error_msg("Please provide a Last.fm username (or set yours with `/setfm`)."))
elif u.is_mention(username):
mentioned = self.get_user(username[3:-1])
try: username = await self.get_lastfm(mentioned.id)
except Exception as e:
print(e)
await ctx.send(u.error_msg(f"Unknown database error:\n{e}"))
if not username:
await ctx.send(u.error_msg(f"[@:{mentioned.id}] doesn't seem to have an account set. Do so with `/setfm`."))
return username

View file

@ -1,8 +0,0 @@
import nerimity
import bot
def setup(bot: bot.Bot):
@bot.command(name="topartists", aliases=["ta","topartist"])
@bot.slash_command(name="topartists")
def topartists(ctx: nerimity.Context, timeframe)

View file

@ -8,100 +8,80 @@ import utils as u
# from catbox_async_uploader.catbox_async_uploader.enums import LitterboxDuration
# from pyzxz import ZeroXZero
async def send_collage(bot: bot.Bot, ctx: nerimity.Context, entity: str, size: str = "3x3", timeframe: str = "7day", username: str = None):
temp_msg = None
def setup(bot: bot.Bot):
async def send_collage(ctx: nerimity.Context, entity: str, size: str = "3x3", timeframe: str = "7day", username: str = None):
temp_msg = None
if 'x' not in size:
await ctx.send(u.error_msg(f"Please provide a valid size.\nie `/chart{entity} 5x5`"))
return
try:
if int(size.split('x')[0]) > 5 or int(size.split('x')[1]) > 5:
await ctx.send(u.error_msg("Maximum size allowed is 5x5."))
return
except ValueError:
await ctx.send(u.error_msg(f"Please provide a valid size.\nie `/chart{entity} 5x5`"))
if not username:
try: username = await bot.get_lastfm(ctx.author.id)
except Exception as e:
print(e)
await ctx.send(u.error_msg(f"Unknown database error:\n{e}"))
if not username:
await ctx.send(u.error_msg("Please provide a Last.fm username (or set yours with `/setfm`)."))
print("returned")
if 'x' not in size:
await ctx.send(u.error_msg(f"Please provide a valid size.\nie `/chart{entity} 5x5`"))
return
if username.startswith("[@:"):
mentioned = bot.get_user(username[3:-1])
try: username = await bot.get_lastfm(mentioned.id)
except Exception as e:
print(e)
await ctx.send(u.error_msg(f"Unknown database error:\n{e}"))
if not username:
await ctx.send(u.error_msg(f"[@:{mentioned.id}] doesn't seem to have an account set. Do so with `/setfm`."))
print("returned")
try:
if int(size.split('x')[0]) > 5 or int(size.split('x')[1]) > 5:
await ctx.send(u.error_msg("Maximum size allowed is 5x5."))
return
match timeframe:
case "a" | "all" | "alltime" | "o": timeframe = "overall"
case "y" | "year" | "yearly": timeframe = "12month"
case "m" | "month" | "monthly": timeframe = "1month"
case "w" | "week" | "weekly": timeframe = "7day"
case "d" | "day" | "daily": timeframe = "1day"
except Exception:
await ctx.send(u.error_msg(f"Please provide a valid size.\nie `/chart{entity} 5x5`"))
return
username = await bot.find_lastfm_username(ctx, username)
if not username: return
case _: "7day"
match timeframe:
case "a" | "all" | "alltime" | "o": timeframe = "overall"
case "y" | "year" | "yearly": timeframe = "12month"
case "m" | "month" | "monthly": timeframe = "1month"
case "w" | "week" | "weekly": timeframe = "7day"
case "d" | "day" | "daily": timeframe = "1day"
if timeframe == "1day":
await ctx.send(u.error_msg("Daily charts are not yet supported. Sorry!!"))
return
case _: "7day"
try:
temp_msg = await ctx.send(f"Generating {entity} chart for **{username}**...")
if timeframe == "1day":
await ctx.send(u.error_msg("Daily charts are not yet supported. Sorry!!"))
return
image = bot.collage_generator.generate(
entity = entity,
username = username,
rows = int(size.split(sep="x")[0]),
cols = int(size.split(sep="x")[1]),
period = timeframe
)
try:
temp_msg = await ctx.send(f"Generating {entity} chart for **{username}**...")
img_bytes = io.BytesIO()
image.save(fp=img_bytes, format="png")
image = bot.collage_generator.generate(
entity = entity,
username = username,
rows = int(size.split(sep="x")[0]),
cols = int(size.split(sep="x")[1]),
period = timeframe
)
# link = await bot.catbox_uploader.upload_to_litterbox(
# file_path_or_bytes = img_bytes,
# file_name = "collage.png",
# duration = LitterboxDuration.H12
# )
img_bytes = io.BytesIO()
image.save(fp=img_bytes, format="png")
# attachment = await u.construct_attachment_from_bytes(
# filename = "chart",
# file_type = "png",
# bytes_arr = img_bytes,
# ).upload()
# link = await bot.catbox_uploader.upload_to_litterbox(
# file_path_or_bytes = img_bytes,
# file_name = "collage.png",
# duration = LitterboxDuration.H12
# )
link = await bot.zxz.upload_from_bytes(img_bytes.getvalue(), f"{entity}_chart.png")
# attachment = await u.construct_attachment_from_bytes(
# filename = "chart",
# file_type = "png",
# bytes_arr = img_bytes,
# ).upload()
await ctx.send(u.good_msg(f"**{size}** {timeframe} {entity} chart for [{username}](https://last.fm/user/{username}) successfully generated:\n{link}"))
link = await bot.zxz.upload_from_bytes(img_bytes.getvalue(), f"{entity}_chart.png")
except Exception as e:
print(e)
await ctx.send(u.error_msg(f"I crashed and burned while generating the chart:\n`{e}`"))
await ctx.send(u.good_msg(f"**{size}** {timeframe} {entity} chart for [{username}](https://last.fm/user/{username}) successfully generated:\n{link}"))
finally:
if temp_msg: temp_msg.delete()
except Exception as e:
print(e)
await ctx.send(u.error_msg(f"I crashed and burned while generating the chart:\n`{e}`"))
finally:
if temp_msg: temp_msg.delete()
def setup(bot: bot.Bot):
@bot.command(name="chart", aliases=["c", "chartalbum", "albumchart", "collage"])
@bot.slash_command(name="chartalbum", description="Generate an album collage.")
async def chartalbum(ctx: nerimity.Context, size: str = "3x3", timeframe: str = "7day", username: str = None):
await send_collage(
bot = bot,
ctx = ctx,
entity = "album",
size = size,
@ -113,7 +93,6 @@ def setup(bot: bot.Bot):
@bot.slash_command(name="chartartist", description="Generate an artist collage.")
async def chartartist(ctx: nerimity.Context, size: str = "3x3", timeframe: str = "7day", username: str = None):
await send_collage(
bot = bot,
ctx = ctx,
entity = "artist",
size = size,
@ -125,7 +104,6 @@ def setup(bot: bot.Bot):
@bot.slash_command(name="charttrack", description="Generate a track/song collage.")
async def charttrack(ctx: nerimity.Context, size: str = "3x3", timeframe: str = "7day", username: str = None):
await send_collage(
bot = bot,
ctx = ctx,
entity = "track",
size = size,

View file

@ -8,26 +8,8 @@ def setup(bot: bot.Bot):
@bot.command(name="fm", aliases=["np"])
@bot.slash_command(name="fm", description="Shows what you're currently playing")
async def fm(ctx: nerimity.Context, username: str = None):
if not username:
try: username = await bot.get_lastfm(ctx.author.id)
except Exception as e:
await ctx.send(u.error_msg(f"Unknown database error:\n{e}"))
if not username:
await ctx.send(u.error_msg("Please provide a Last.fm username (or set yours with `/setfm`)"))
return
if username.startswith("[@:"):
mentioned = bot.get_user(username[3:-1])
try: username = await bot.get_lastfm(mentioned.id)
except Exception as e:
print(e)
await ctx.send(u.error_msg(f"Unknown database error:\n{e}"))
if not username:
await ctx.send(u.error_msg(f"[@:{mentioned.id}] doesn't seem to have an account set. Do so with `/setfm`."))
print("returned")
return
username = await bot.find_lastfm_username(ctx, username)
if not username: return
try:
user = bot.lastfm.get_user(username)

120
commands/top.py Normal file
View file

@ -0,0 +1,120 @@
import nerimity
import pylast
import bot
import utils as u
def setup(bot: bot.Bot):
async def send_top(ctx: nerimity.Context, entity: str, timeframe: str, username: str, countstr: str):
try: count = int(countstr)
except ValueError:
await ctx.send(u.error_msg("Please input a valid number for `count`."))
return
except Exception as e:
print(e)
await ctx.send(u.error_msg(f"Unknown error:\n`{e}`"))
return
if count > 10 or count < 1:
await ctx.send(u.error_msg("Please enter a count number between 1 and 10."))
return
requested = True if username else False
username = await bot.find_lastfm_username(ctx, username)
if not username: return
match timeframe:
case "a" | "all" | "alltime" | "o":
timeframe = "overall"
period = timeframe
case "y" | "year" | "yearly":
timeframe = "12month"
period = "in the past year"
case "m" | "month" | "monthly":
timeframe = "1month"
period = "in the past month"
case "w" | "week" | "weekly":
timeframe = "7day"
period = "in the past week"
case "d" | "day" | "daily":
timeframe = "1day"
period = "in the past day"
case _:
timeframe = "7day"
period = "in the past week (default)"
try:
user = bot.lastfm.get_user(username)
msg_content = ""
match entity:
case "artist":
top_list = user.get_top_artists(period=timeframe, limit=count)
for index, i in enumerate(top_list):
msg_content += f"\n{index+1}. [{i.item.get_name(properly_capitalized=True)}]({i.item.get_url()}) | {i.weight} plays"
case "album":
top_list = user.get_top_albums(period=timeframe, limit=count)
for index, i in enumerate(top_list):
msg_content += f"\n{index+1}. [{i.item.get_title(properly_capitalized=True)}]({i.item.get_url()}) by [{i.item.get_artist().get_name(properly_capitalized=True)}]({i.item.get_artist().get_url()}) | {i.weight} plays"
case "track":
top_list = user.get_top_tracks(period=timeframe, limit=count)
for index, i in enumerate(top_list):
msg_content += f"\n{index+1}. [{i.item.get_title(properly_capitalized=True)}]({i.item.get_url()}) by [{i.item.get_artist().get_name(properly_capitalized=True)}]({i.item.get_artist().get_url()}) | {i.weight} plays"
msg_head = f"Top {len(top_list)} {entity}s {period} for [{username}]({user.get_url()}):\n"
msg = msg_head + msg_content
if requested is True:
msg += f"\n### Requested by [@:{ctx.author.id}]"
await ctx.send(msg)
except pylast.WSError as e:
print(e)
await ctx.send(u.error_msg(f"Last.fm error:\n`{e}`"))
return
except Exception as e:
print(e)
await ctx.send(u.error_msg(f"Unknown error:\n`{e}`"))
return
@bot.command(name="topartists", aliases=["ta","topartist"])
@bot.slash_command(name="topartists", description="Generate a list of your most played artists.")
async def topartists(ctx: nerimity.Context, timeframe: str = "7day", username: str = None, countstr: str = "10"):
await send_top(
ctx = ctx,
entity = "artist",
timeframe = timeframe,
username = username,
countstr = countstr
)
@bot.command(name="topalbums", aliases=["t","top","topalbum"])
@bot.slash_command(name="topalbums", description="Generate a list of your most played albums.")
async def topalbums(ctx: nerimity.Context, timeframe: str = "7day", username: str = None, countstr: str = "10"):
await send_top(
ctx = ctx,
entity = "album",
timeframe = timeframe,
username = username,
countstr = countstr
)
@bot.command(name="toptracks", aliases=["tt","toptrack"])
@bot.slash_command(name="toptracks", description="Generate a list of your most played songs.")
async def toptracks(ctx: nerimity.Context, timeframe: str = "7day", username: str = None, countstr: str = "10"):
await send_top(
ctx = ctx,
entity = "track",
timeframe = timeframe,
username = username,
countstr = countstr
)

View file

@ -37,3 +37,8 @@ def error_msg(message: str) -> str:
def good_msg(message: str) -> str:
return f"[#52ff54][Success] [#reset]{message}"
def is_mention(mention: str) -> bool:
if not mention: return None
return True if mention.startswith("[@:") else False