Skip to content

Latest commit

 

History

31 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

lavalink.lua

Feature-rich Lavalink v4 client for Luvit (Lua) Discord bots - multi-node support, queue management, audio filters, session resuming and Discordia / discord.lua integrations.


Installation

lit install filispeen/lavalink.lua

For the example bot, also install Discordia:

lit install SinisterRectus/discordia

lavalink.lua currently supports ONLY my custom Discord API wrapper filispeen/discord.lua and SinisterRectus/discordia (first supported library).


Requirements

  • Luvit runtime + Lit package manager
  • Lavalink v4 server (or Lavalink v3.7+ with LAVALINK_API_VERSION=3)

Quick Start

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()

Events Reference

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
nodeLinkReady node, info Node was identified as NodeLink
sponsorBlockSegmentsLoaded player, segments, data NodeLink loaded SponsorBlock segments
sponsorBlockSegmentSkipped player, segment, data NodeLink skipped a segment
mixStart / mixEnd player, data NodeLink mixer lifecycle event
lyricsFound player, lyrics, data Lyrics were resolved for the current track
lyricsLine player, lineIndex, data Synchronized lyrics advanced a line
voiceReceiveFrame node, guildId, frame, message Raw NodeLink Opus or PCM voice frame
nodeLinkEvent node, player?, data A NodeLink event without a dedicated convenience event

Player API

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 Lavalink

-- NodeLink filters
player.filters:setEcho({ delay = 500, feedback = 0.3, mix = 0.5 })
player.filters:setChorus({ rate = 1.5, depth = 0.4, delay = 20, mix = 0.5 })
player.filters:setCompressor({ threshold = -18, ratio = 3, attack = 10, release = 100, gain = 0 })
player.filters:setHighPass({ smoothing = 2.0 })
player.filters:setPhaser({ stages = 4, rate = 0.5, depth = 0.6, feedback = 0.3, mix = 0.5 })
player.filters:setSpatial(options)

LavalinkManager API

lavalink: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?)

-- NodeLink helpers
lavalink:refreshNodeInfo(nodeId?)
lavalink:getNodeLinkConnection(nodeId?)
lavalink:getNodeLinkWorkers(nodeId?)
lavalink:patchNodeLinkWorker(data, nodeId?)
lavalink:getLyrics(encodedTrack, { node?, lang? })
lavalink:getChapters(encodedTrack, nodeId?)
lavalink:getMeaning(encodedTrack, { node?, lang? })
lavalink:getTrackStream(encodedTrack, { node?, itag? })

lavalink:handleVoiceUpdate(packet)         -- Feed raw VOICE_STATE_UPDATE / VOICE_SERVER_UPDATE

NodeLink extensions

When a node becomes ready, node.isNodeLink is set from /v4/info; listen to nodeLinkReady if the application needs to wait for that detection. All NodeLink methods are opt-in, so normal Lavalink v4 nodes are unaffected.

-- Metadata for the current track
local lyrics = player:getLyrics("uk")
local chapters = player:getChapters()

-- SponsorBlock
player:updateSponsorBlock({ enabled = true, categories = { "sponsor", "intro" } })
player:setSponsorBlockSegments(segments)    -- optional custom segments
player:subscribeLyrics(true)                -- emits lyricsFound / lyricsLine

-- Overlay a TTS/SFX/music track. `track` can be a result from loadtracks.
local mix = player:addMix(track, 0.8)
player:updateMix(mix.mixId, 0.5)
player:removeMix(mix.mixId)

-- Select an alternate audio track and preload the next encoded track.
player:play({ audioTrackId = "en.4", nextTrack = nextTrack })

-- Experimental voice receive: configure voiceReceive in NodeLink first.
player:startVoiceReceive(function(guildId, frame)
  -- frame is a binary Opus or PCM S16LE payload, per NodeLink configuration.
end)

The additional source prefixes (for example spsearch:, dzsearch:, gtts: and search:) already work through lavalink:search, because its source option is passed directly to NodeLink:

local result = lavalink:search("never gonna give you up", { source = "spsearch" })

createPlayer options:

{
  guildId        = "...",   -- required
  voiceChannelId = "...",
  textChannelId  = "...",
  selfDeaf       = true,    -- default true
  selfMute       = false,
  node           = "main",  -- optional node id, defaults to least-loaded
  region         = "europe",
  volume         = 100,
}

Node Options

{
  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
}

Lavalink API version

The client uses Lavalink v4 by default. To use a Lavalink v3.7+ node, set the environment variable before starting the bot:

LAVALINK_API_VERSION=3 luvit bot.lua

Or choose a version in code; a node-level apiVersion overrides the manager default:

local lavalink = lavalinklua.discordia(client, {
  apiVersion = 3, -- accepts only 3 or 4; default is 4
  -- nodes = { { apiVersion = 4, ... } }, -- optional per-node override
})

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.


Example Bots

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.lua

discord.lua:

cd example/discord.lua
cp .env.example .env
# fill in .env with your values
luvit bot.lua

License

MIT

About

Feature-rich Lavalink v4 client for Luvit (Lua)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages