feat: playlist import, show, and shuffle-on-load
Add !playlist import <name> <url> to resolve and save tracks from a URL without queueing them. Add !playlist show <name> to display tracks in a saved playlist via long_reply (auto-pastes on overflow). Add optional 'shuffle' keyword to !playlist load for randomized playback order.
This commit is contained in:
@@ -1676,13 +1676,15 @@ async def _kept_repair(bot, message) -> None:
|
||||
|
||||
@command("playlist", help="Music: !playlist save|load|list|del <name>")
|
||||
async def cmd_playlist(bot, message):
|
||||
"""Save, load, list, or delete named playlists.
|
||||
"""Save, load, list, delete, import, or show named playlists.
|
||||
|
||||
Usage:
|
||||
!playlist save <name> Save current + queued tracks as a playlist
|
||||
!playlist load <name> Append saved playlist to queue and start playback
|
||||
!playlist list Show saved playlists with track counts
|
||||
!playlist del <name> Delete a saved playlist
|
||||
!playlist save <name> Save current + queued tracks as a playlist
|
||||
!playlist load <name> [shuffle] Append saved playlist to queue
|
||||
!playlist list Show saved playlists with track counts
|
||||
!playlist del <name> Delete a saved playlist
|
||||
!playlist import <name> <url> Import tracks from URL as a named playlist
|
||||
!playlist show <name> Display tracks in a saved playlist
|
||||
"""
|
||||
if not _is_mumble(bot):
|
||||
await bot.reply(message, "Mumble-only feature")
|
||||
@@ -1691,7 +1693,7 @@ async def cmd_playlist(bot, message):
|
||||
parts = message.text.split()
|
||||
if len(parts) < 2:
|
||||
await bot.reply(
|
||||
message, "Usage: !playlist save|load|list|del <name>",
|
||||
message, "Usage: !playlist save|load|list|del|import|show <name>",
|
||||
)
|
||||
return
|
||||
|
||||
@@ -1723,9 +1725,10 @@ async def cmd_playlist(bot, message):
|
||||
|
||||
elif sub == "load":
|
||||
if len(parts) < 3:
|
||||
await bot.reply(message, "Usage: !playlist load <name>")
|
||||
await bot.reply(message, "Usage: !playlist load <name> [shuffle]")
|
||||
return
|
||||
name = parts[2].lower()
|
||||
shuffle = len(parts) >= 4 and parts[3].lower() == "shuffle"
|
||||
raw = bot.state.get("music", f"playlist:{name}")
|
||||
if not raw:
|
||||
await bot.reply(message, f"No playlist named '{name}'")
|
||||
@@ -1746,9 +1749,12 @@ async def cmd_playlist(bot, message):
|
||||
requester=e.get("requester", "?"),
|
||||
))
|
||||
added += 1
|
||||
if shuffle and ps["queue"]:
|
||||
random.shuffle(ps["queue"])
|
||||
suffix = " (shuffled)" if shuffle else ""
|
||||
await bot.reply(
|
||||
message,
|
||||
f"Loaded '{name}': {added} track{'s' if added != 1 else ''}",
|
||||
f"Loaded '{name}': {added} track{'s' if added != 1 else ''}{suffix}",
|
||||
)
|
||||
if was_idle:
|
||||
_ensure_loop(bot)
|
||||
@@ -1791,9 +1797,58 @@ async def cmd_playlist(bot, message):
|
||||
bot.state.delete("music", f"playlist:{name}")
|
||||
await bot.reply(message, f"Deleted playlist '{name}'")
|
||||
|
||||
elif sub == "import":
|
||||
if len(parts) < 4:
|
||||
await bot.reply(message, "Usage: !playlist import <name> <url>")
|
||||
return
|
||||
name = parts[2].lower()
|
||||
url = parts[3]
|
||||
await bot.reply(message, f"Importing '{name}' from URL...")
|
||||
loop = asyncio.get_running_loop()
|
||||
try:
|
||||
resolved = await loop.run_in_executor(None, _resolve_tracks, url)
|
||||
except Exception:
|
||||
await bot.reply(message, "Failed to resolve URL")
|
||||
return
|
||||
if not resolved:
|
||||
await bot.reply(message, "No tracks found")
|
||||
return
|
||||
requester = message.nick or "?"
|
||||
entries = [{"url": u, "title": t, "requester": requester}
|
||||
for u, t in resolved]
|
||||
bot.state.set("music", f"playlist:{name}", json.dumps(entries))
|
||||
await bot.reply(
|
||||
message,
|
||||
f"Imported playlist '{name}' ({len(entries)} track"
|
||||
f"{'s' if len(entries) != 1 else ''})",
|
||||
)
|
||||
|
||||
elif sub == "show":
|
||||
if len(parts) < 3:
|
||||
await bot.reply(message, "Usage: !playlist show <name>")
|
||||
return
|
||||
name = parts[2].lower()
|
||||
raw = bot.state.get("music", f"playlist:{name}")
|
||||
if not raw:
|
||||
await bot.reply(message, f"No playlist named '{name}'")
|
||||
return
|
||||
try:
|
||||
entries = json.loads(raw)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
await bot.reply(message, f"Corrupt playlist '{name}'")
|
||||
return
|
||||
if not entries:
|
||||
await bot.reply(message, f"Playlist '{name}' is empty")
|
||||
return
|
||||
lines = [f"Playlist '{name}' ({len(entries)} tracks):"]
|
||||
for i, e in enumerate(entries, 1):
|
||||
title = _truncate(e.get("title", e["url"]))
|
||||
lines.append(f" {i:>2}. {title}")
|
||||
await bot.long_reply(message, lines, label=name)
|
||||
|
||||
else:
|
||||
await bot.reply(
|
||||
message, "Usage: !playlist save|load|list|del <name>",
|
||||
message, "Usage: !playlist save|load|list|del|import|show <name>",
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user