Slash Commands Bot¶
Modern Discord bots use slash commands (/play) instead of prefix commands (!play). Here's how to build a slash-command music bot with Voltricx.
slash_bot.py
import os
import discord
from discord import app_commands
from dotenv import load_dotenv
import voltricx
load_dotenv()
# ── Bot Setup ──────────────────────────────────────────────────────────────
class SlashMusicBot(discord.Client):
def __init__(self):
intents = discord.Intents.default()
intents.voice_states = True
super().__init__(intents=intents)
self.tree = app_commands.CommandTree(self)
async def setup_hook(self):
await voltricx.Pool.connect(
client=self,
nodes=[
voltricx.NodeConfig(
identifier="Main",
uri=os.getenv("LAVALINK_URI", "http://localhost:2333"),
password=os.getenv("LAVALINK_PASSWORD", "youshallnotpass"),
)
],
default_search_source="dzsearch",
)
await self.tree.sync()
async def close(self):
await voltricx.Pool.close()
await super().close()
client = SlashMusicBot()
# ── Events ─────────────────────────────────────────────────────────────────
@client.event
async def on_ready():
print(f"✅ {client.user} ready with slash commands")
@client.event
async def on_voltricx_track_start(player: voltricx.Player, track: voltricx.Playable):
if player.home:
await player.home.send(f"🎵 Now playing: **{track.title}**")
@client.event
async def on_voltricx_inactive_player(player: voltricx.Player):
await player.disconnect()
# ── Helper: Ensure user is in a VC ────────────────────────────────────────
async def ensure_voice(interaction: discord.Interaction) -> voltricx.Player | None:
"""Connect if needed and return the player. Returns None if user not in VC."""
if not interaction.user.voice:
await interaction.response.send_message("❌ Join a voice channel first!", ephemeral=True)
return None
if interaction.guild.voice_client:
return interaction.guild.voice_client # type: ignore
player: voltricx.Player = await interaction.user.voice.channel.connect(cls=voltricx.Player)
player.home = interaction.channel
player.inactive_timeout = 300
return player
# ── Slash Commands ─────────────────────────────────────────────────────────
@client.tree.command(name="play", description="Search and play a track or playlist")
@app_commands.describe(query="Song name, URL, or search query")
async def play(interaction: discord.Interaction, query: str):
await interaction.response.defer()
player = await ensure_voice(interaction)
if not player:
return
results = await voltricx.Pool.fetch_tracks(query)
if not results:
return await interaction.followup.send(f"❌ No results for `{query}`")
if isinstance(results, voltricx.Playlist):
count = player.queue.put(results)
await interaction.followup.send(f"📋 Added **{results.name}** ({count} tracks)")
else:
track = results[0]
player.queue.put(track)
await interaction.followup.send(f"➕ Added **{track.title}** to queue")
if not player.playing:
await player.play(player.queue.get())
@client.tree.command(name="skip", description="Skip the current track")
async def skip(interaction: discord.Interaction):
if not interaction.guild.voice_client:
return await interaction.response.send_message("❌ Not in a voice channel.", ephemeral=True)
player: voltricx.Player = interaction.guild.voice_client
old = player.current
await player.skip()
await interaction.response.send_message(f"⏭️ Skipped **{old.title if old else 'track'}**")
@client.tree.command(name="pause", description="Pause or resume playback")
async def pause(interaction: discord.Interaction):
player: voltricx.Player = interaction.guild.voice_client
if not player:
return await interaction.response.send_message("❌ Not connected.", ephemeral=True)
new_state = not player.paused
await player.pause(new_state)
await interaction.response.send_message("⏸️ Paused" if new_state else "▶️ Resumed")
@client.tree.command(name="volume", description="Set the volume (0–1000)")
@app_commands.describe(level="Volume level (0 = mute, 100 = normal, 200 = double)")
async def volume(interaction: discord.Interaction, level: app_commands.Range[int, 0, 1000]):
player: voltricx.Player = interaction.guild.voice_client
if not player:
return await interaction.response.send_message("❌ Not connected.", ephemeral=True)
await player.set_volume(level)
await interaction.response.send_message(f"🔊 Volume set to **{level}%**")
@client.tree.command(name="nowplaying", description="Show the currently playing track")
async def nowplaying(interaction: discord.Interaction):
player: voltricx.Player = interaction.guild.voice_client
if not player or not player.current:
return await interaction.response.send_message("Nothing is playing.", ephemeral=True)
track = player.current
pos, length = player.position, track.length
filled = int((pos / length) * 20) if length else 0
bar = "▬" * filled + "🔘" + "▬" * max(0, 20 - filled)
fmt = lambda ms: f"{ms // 60000}:{(ms // 1000) % 60:02d}"
embed = discord.Embed(
title="🎵 Now Playing",
description=f"**[{track.title}]({track.uri})**\nby {track.author}",
color=0x7c3aed,
)
embed.add_field(name="Progress", value=f"`{fmt(pos)}` {bar} `{fmt(length)}`", inline=False)
embed.add_field(name="Volume", value=f"{player.volume}%")
embed.add_field(name="Queue", value=f"{len(player.queue)} tracks")
if track.artwork:
embed.set_thumbnail(url=track.artwork)
await interaction.response.send_message(embed=embed)
@client.tree.command(name="queue", description="Show the current queue")
async def queue(interaction: discord.Interaction):
player: voltricx.Player = interaction.guild.voice_client
if not player or not player.queue:
return await interaction.response.send_message("Queue is empty.", ephemeral=True)
tracks = player.queue[0:10]
lines = [f"`{i+1}.` {t.title}" for i, t in enumerate(tracks)]
embed = discord.Embed(
title=f"📋 Queue — {len(player.queue)} tracks",
description="\n".join(lines),
color=0x7c3aed,
)
if player.current:
embed.set_author(name=f"▶ {player.current.title}")
await interaction.response.send_message(embed=embed)
@client.tree.command(name="filter", description="Apply an audio filter preset")
@app_commands.describe(preset="Choose a preset")
@app_commands.choices(preset=[
app_commands.Choice(name="🌃 Nightcore", value="nightcore"),
app_commands.Choice(name="🌊 Vaporwave", value="vaporwave"),
app_commands.Choice(name="🎙️ 8D Audio", value="8d"),
app_commands.Choice(name="🔊 Bass Boost", value="bassboost"),
app_commands.Choice(name="🎤 Karaoke", value="karaoke"),
app_commands.Choice(name="✨ Reset", value="reset"),
])
async def filter_cmd(interaction: discord.Interaction, preset: str):
player: voltricx.Player = interaction.guild.voice_client
if not player:
return await interaction.response.send_message("❌ Not connected.", ephemeral=True)
filters = player.filters
if preset == "nightcore":
filters.timescale.set(speed=1.3, pitch=1.3)
label = "🌃 Nightcore"
elif preset == "vaporwave":
filters.timescale.set(speed=0.8, pitch=0.8)
label = "🌊 Vaporwave"
elif preset == "8d":
filters.rotation.set(rotation_hz=0.2)
label = "🎙️ 8D Audio"
elif preset == "bassboost":
for i in range(4):
filters.equalizer.set_band(band=i, gain=0.25)
label = "🔊 Bass Boost"
elif preset == "karaoke":
filters.karaoke.set(level=1.0, mono_level=1.0)
label = "🎤 Karaoke"
else:
filters = voltricx.Filters()
label = "✨ Reset"
await player.set_filters(filters)
await interaction.response.send_message(f"Applied filter: **{label}**")
@client.tree.command(name="disconnect", description="Disconnect the bot from voice")
async def disconnect(interaction: discord.Interaction):
player: voltricx.Player = interaction.guild.voice_client
if player:
await player.disconnect()
await interaction.response.send_message("👋 Disconnected.")
client.run(os.getenv("TOKEN"))