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>")
|
@command("playlist", help="Music: !playlist save|load|list|del <name>")
|
||||||
async def cmd_playlist(bot, message):
|
async def cmd_playlist(bot, message):
|
||||||
"""Save, load, list, or delete named playlists.
|
"""Save, load, list, delete, import, or show named playlists.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
!playlist save <name> Save current + queued tracks as a playlist
|
!playlist save <name> Save current + queued tracks as a playlist
|
||||||
!playlist load <name> Append saved playlist to queue and start playback
|
!playlist load <name> [shuffle] Append saved playlist to queue
|
||||||
!playlist list Show saved playlists with track counts
|
!playlist list Show saved playlists with track counts
|
||||||
!playlist del <name> Delete a saved playlist
|
!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):
|
if not _is_mumble(bot):
|
||||||
await bot.reply(message, "Mumble-only feature")
|
await bot.reply(message, "Mumble-only feature")
|
||||||
@@ -1691,7 +1693,7 @@ async def cmd_playlist(bot, message):
|
|||||||
parts = message.text.split()
|
parts = message.text.split()
|
||||||
if len(parts) < 2:
|
if len(parts) < 2:
|
||||||
await bot.reply(
|
await bot.reply(
|
||||||
message, "Usage: !playlist save|load|list|del <name>",
|
message, "Usage: !playlist save|load|list|del|import|show <name>",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1723,9 +1725,10 @@ async def cmd_playlist(bot, message):
|
|||||||
|
|
||||||
elif sub == "load":
|
elif sub == "load":
|
||||||
if len(parts) < 3:
|
if len(parts) < 3:
|
||||||
await bot.reply(message, "Usage: !playlist load <name>")
|
await bot.reply(message, "Usage: !playlist load <name> [shuffle]")
|
||||||
return
|
return
|
||||||
name = parts[2].lower()
|
name = parts[2].lower()
|
||||||
|
shuffle = len(parts) >= 4 and parts[3].lower() == "shuffle"
|
||||||
raw = bot.state.get("music", f"playlist:{name}")
|
raw = bot.state.get("music", f"playlist:{name}")
|
||||||
if not raw:
|
if not raw:
|
||||||
await bot.reply(message, f"No playlist named '{name}'")
|
await bot.reply(message, f"No playlist named '{name}'")
|
||||||
@@ -1746,9 +1749,12 @@ async def cmd_playlist(bot, message):
|
|||||||
requester=e.get("requester", "?"),
|
requester=e.get("requester", "?"),
|
||||||
))
|
))
|
||||||
added += 1
|
added += 1
|
||||||
|
if shuffle and ps["queue"]:
|
||||||
|
random.shuffle(ps["queue"])
|
||||||
|
suffix = " (shuffled)" if shuffle else ""
|
||||||
await bot.reply(
|
await bot.reply(
|
||||||
message,
|
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:
|
if was_idle:
|
||||||
_ensure_loop(bot)
|
_ensure_loop(bot)
|
||||||
@@ -1791,9 +1797,58 @@ async def cmd_playlist(bot, message):
|
|||||||
bot.state.delete("music", f"playlist:{name}")
|
bot.state.delete("music", f"playlist:{name}")
|
||||||
await bot.reply(message, f"Deleted 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:
|
else:
|
||||||
await bot.reply(
|
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