Feature-rich Lavalink v4 client for Luvit (Lua) Discord bots - multi-node support, queue management, audio filters, session resuming and Discordia / discord.lua integrations.
lit install filispeen/lavalink.luaFor the example bot, also install Discordia:
lit install SinisterRectus/discordiaUsing the discord.lua integration instead does not require Discordia; install discord.lua per its own README.
local discordia = require("discordia")
local lavalinklua = require("lavalink.lua")
local client = discordia.Client()
client:on("ready", function()
local lavalink = lavalinklua.discordia(client, {
clientId = client.user.id,
nodes = {
{
id = "main",
host = "localhost",
port = 2333,
authorization = "youshallnotpass",
},
},
})
lavalink:on("trackStart", function(player, track)
print("Now playing: " .. track.info.title)
end)
lavalink:on("queueEnd", function(player)
player:destroy("queue finished")
end)
lavalink:init()
end)
client:run("Bot TOKEN")Or with discord.lua instead of Discordia:
local Bot = require("discord.lua")
local lavalinklua = require("lavalink.lua")
local bot = Bot("Bot TOKEN")
bot:on("ready", function()
local lavalink = lavalinklua.discord(bot, {
nodes = {
{
id = "main",
host = "localhost",
port = 2333,
authorization = "youshallnotpass",
},
},
})
lavalink:on("trackStart", function(player, track)
print("Now playing: " .. track.info.title)
end)
lavalink:on("queueEnd", function(player)
player:destroy("queue finished")
end)
lavalink:init()
end)
bot:run()Playing a track:
local player, created = lavalink:createPlayer({
guildId = guildId,
voiceChannelId = voiceChannelId,
textChannelId = textChannelId,
selfDeaf = true,
})
if created then player:connect() end
local result = lavalink:search("never gonna give you up")
player.queue:add(result.data[1])
player:play()| Event | Arguments | Description |
|---|---|---|
nodeReady |
node, resumed, sessionId |
Node WebSocket ready |
nodeConnect |
node |
WebSocket connection opened |
nodeDisconnect |
node, reason |
WebSocket disconnected |
nodeReconnecting |
node, attempt, delayMs |
Reconnect scheduled |
nodeError |
node, error |
Node-level error |
nodeStats |
node, stats |
Periodic server stats |
playerCreate |
player |
Player created |
playerDestroy |
player, reason |
Player destroyed |
playerUpdate |
player, state |
Position/ping update |
playerPause |
player |
Player paused |
playerResume |
player |
Player resumed |
playerRepeat |
player, mode |
Repeat mode changed |
playerMoved |
player, oldNode, newNode |
Player moved to new node |
trackStart |
player, track |
Track began playing |
trackEnd |
player, track, reason |
Track ended |
trackError |
player, track, error |
Track exception / load failed |
trackStuck |
player, track, thresholdMs |
Track stuck |
queueEnd |
player |
Queue finished |
socketClosed |
player, code, reason, byRemote |
Discord voice WS closed |
error |
player, error |
Generic player error |
player:connect() -- Send OP4 to Discord (join voice)
player:disconnect(destroyPlayer?) -- Leave voice channel
player:destroy(reason?) -- Delete player on Lavalink + cleanup
player:play(options?) -- Play current/next track
player:pause(state?) -- Toggle or set pause
player:resume() -- Alias for pause(false)
player:stop() -- Stop without clearing queue
player:stopPlaying(clearQueue?) -- Stop + optionally clear queue
player:skip(skipTo?, throwError?) -- Skip to Nth track
player:seek(positionMs) -- Seek to position
player:setVolume(0-1000) -- Set volume
player:setRepeatMode("off"|"track"|"queue")
player:moveToNode(nodeId) -- Live-migrate to another node
player:getPosition() -- Client-side interpolated position (ms)
-- Queue
player.queue:add(track|tracks)
player.queue:remove(startIndex, endIndex?)
player.queue:shuffle()
player.queue:clear()
player.queue.current -- current track
player.queue.tracks -- upcoming tracks
player.queue.previous -- last 25 played
-- Filters
player.filters:setVolume(multiplier)
player.filters:setEqualizer(bands) -- { {band=0, gain=0.35}, ... }
player.filters:setTimescale({ speed, pitch, rate })
player.filters:setRotation({ rotationHz })
player.filters:setKaraoke(options)
player.filters:setTremolo(options)
player.filters:setVibrato(options)
player.filters:setDistortion(options)
player.filters:setChannelMix(options)
player.filters:setLowPass(options)
player.filters:setPluginFilters(table)
player.filters:resetFilters()
player.filters:resetFilter(filterName)
player.filters:apply() -- Re-send current filter state to Lavalinklavalink:addNode(options) -- Add a node at runtime
lavalink:removeNode(id) -- Disconnect and remove a node
lavalink:init() -- Connect all configured nodes
lavalink:getNode(id?) -- Get node by id, or least-loaded usable node
lavalink:getUsableNodes() -- List of connected + ready nodes
lavalink:getAllNodes() -- List of all nodes regardless of state
lavalink:createPlayer(options) -- Create (or get existing) player for a guild
lavalink:getPlayer(guildId) -- Get existing player, or nil
lavalink:destroyPlayer(guildId, reason?) -- Destroy player for a guild
lavalink:search(query, options?) -- REST loadTracks, options = { source?, node? }
lavalink:decodeTrack(encoded, nodeId?)
lavalink:decodeTracks(encodedList, nodeId?)
lavalink:handleVoiceUpdate(packet) -- Feed raw VOICE_STATE_UPDATE / VOICE_SERVER_UPDATEcreatePlayer options:
{
guildId = "...", -- required
voiceChannelId = "...",
textChannelId = "...",
selfDeaf = true, -- default true
selfMute = false,
node = "main", -- optional node id, defaults to least-loaded
region = "europe",
volume = 100,
}{
id = "main", -- defaults to "host:port"
host = "localhost",
port = 2333,
authorization = "youshallnotpass",
secure = false, -- use wss/https
resuming = true, -- enable session resuming
resumeTimeout = 60, -- seconds
reconnectTries = 5,
reconnectDelay = 5000, -- ms, doubles on each attempt up to 60s
regions = { "eu-west" }, -- used by region-aware node selection
}If you're not using the Discordia or discord.lua integration, call lavalink:handleVoiceUpdate(packet) yourself for every VOICE_STATE_UPDATE and VOICE_SERVER_UPDATE gateway event, and provide sendPayload = function(guildId, payload) ... end in the manager options to forward voice payloads (OP4) to your gateway.
The example/ folder contains two fully working bots with the same music commands, one per integration.
Discordia:
cd example/discordia
cp .env.example .env
# fill in .env with your values
luvit bot.luadiscord.lua:
cd example/discord.lua
cp .env.example .env
# fill in .env with your values
luvit bot.luaMIT