dreama-link
v1.0.2
Published
A powerful but simple, Lavalink v4 client for Discord.js bots! supports ESM & CJS, full REST API, filters, queues, session resuming, DAVE E2EE, and plugin support.
Maintainers
Readme
dreama-link
A powerful but simple, Lavalink v4 client library for Discord.js bots.
Supports ESM and CommonJS, session resuming, multi-node load balancing, a fluent filter builder, a full REST API wrapper, a built-in track queue, DAVE E2EE, and auto-detected Lavalink plugins.
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Core Concepts
- API Reference
- Events
- Plugin Support
- DAVE E2EE Protocol
- Ready Event Compatibility
- Types Reference
- ESM & CJS Usage
- Advanced Usage
- Search Sources
- License
Features
- 🔗 Full Lavalink v4 protocol support (WebSocket + REST)
- 📦 ESM and CommonJS dual-package support
- 🚀 Discord.js v14 and v15 compatible (supports both
readyandclientReady) - ✨ Complete REST API wrapper with typed responses
- 🔌 Powerful FilterBuilder — nightcore, vaporwave, bass boost, and full manual control
- 🔍 Built-in Queue with loop modes (track, queue, none), shuffle, history, and more
- 🤹♂️ Multi-node support with automatic load balancing via penalty scoring
- ‼️ Session resuming — music keeps playing through bot restarts
- 🟢 Automatic reconnection with configurable exponential backoff
- 💪 Strongly typed with full TypeScript definitions
- 🛡️ DAVE E2EE protocol support (Discord Audio & Video End-to-End Encryption)
- 🔧 Auto-detected plugins with colored console output on node connect
- 🎛️ SponsorBlock — skip segments, load chapters, full REST and event support
- 🎤 LavaLyrics — fetch real-time lyrics for any track
- 🎵 LavaSrc — Spotify, Apple Music, Deezer, Yandex Music, JioSaavn search
- 🔎 LavaSearch — multi-source search in a single call
- 🚪 Auto-disconnect —
leaveOnEmptyandleaveOnEndbuilt in
Requirements
- Node.js >= 18.0.0
- A running Lavalink v4 server
- discord.js >= 14.0.0 (peer dependency, v15 also supported)
Installation
npm install dreama-link
# or
yarn add dreama-link
# or
pnpm add dreama-linkQuick Start
import { Client, GatewayIntentBits, Events } from 'discord.js';
import { Manager } from 'dreama-link';
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
const manager = new Manager({
nodes: [
{
host: 'localhost',
port: 2333,
password: 'youshallnotpass',
secure: false,
name: 'main',
retryAmount: 5,
retryDelay: 3000,
resumeTimeout: 60,
},
],
autoPlay: true,
defaultVolume: 100,
defaultSelfDeaf: true,
leaveOnEmpty: true,
leaveOnEmptyDelay: 30000,
leaveOnEnd: false,
send(guildId, payload) {
const guild = client.guilds.cache.get(guildId);
guild?.shard.send(payload);
},
});
// Works with both discord.js v14 and v15
client.once(Events.ClientReady, (c) => {
manager.init(c.user.id);
console.log(`Logged in as ${c.user.tag}`);
});
client.on('raw', (data) => {
if (data.t === 'VOICE_SERVER_UPDATE') manager.handleVoiceServerUpdate(data.d);
if (data.t === 'VOICE_STATE_UPDATE') manager.handleVoiceStateUpdate(data.d);
});
manager.on('nodeReady', (node) => {
console.log(`Node ${node.name} is ready`);
});
manager.on('trackStart', (player, track) => {
console.log(`Now playing: ${track.info.title} by ${track.info.author}`);
});
manager.on('queueEnd', (player) => {
console.log(`Queue ended in guild ${player.guildId}`);
});
manager.on('channelEmpty', (player) => {
console.log(`Voice channel empty in guild ${player.guildId} — leaving...`);
});
client.on('messageCreate', async (message) => {
if (!message.guild || message.author.bot) return;
if (!message.content.startsWith('!')) return;
const args = message.content.slice(1).trim().split(/ +/g);
const command = args.shift()?.toLowerCase();
if (command === 'play') {
const voiceChannel = message.member?.voice.channel;
if (!voiceChannel) return message.reply('Join a voice channel first!');
let player = manager.getPlayer(message.guild.id);
if (!player) {
player = manager.createPlayer({
guildId: message.guild.id,
voiceChannelId: voiceChannel.id,
textChannelId: message.channel.id,
});
await player.connect();
}
const query = args.join(' ');
const result = await manager.search({ source: 'youtube', query });
if (result.loadType === 'error') return message.reply('Failed to load track.');
if (result.loadType === 'empty') return message.reply('No results found.');
if (result.loadType === 'playlist') {
player.queue.add(result.data.tracks);
message.reply(`Added playlist **${result.data.info.name}** (${result.data.tracks.length} tracks)`);
} else {
const track = result.loadType === 'search' ? result.data[0] : result.data;
player.queue.add(track);
message.reply(`Added **${track.info.title}** to the queue`);
}
if (!player.playing) await player.play();
}
});
client.login('YOUR_BOT_TOKEN');Core Concepts
Manager
The Manager is the central hub. It manages all nodes, players, voice state routing, and optional features like DAVE and auto-disconnect. You create one Manager per bot process and call manager.init(clientId) once the Discord client is ready.
[!WARNING] If there is no
clientIdgiven, voice states, players, and nodes won't be initialized.
Node
A Node represents a connection to a single Lavalink server. The Manager maintains a pool of nodes and automatically routes players to the least-loaded one. On connect, each node's installed plugins are auto-detected and printed to the console with color.
Player
A Player controls audio playback for a single Discord guild. It holds a Queue, current playback state, filters, and voice connection info. Create players via manager.createPlayer(). Calling player.connect() before player.play() is required — play() now waits automatically for the voice connection to be fully established with Lavalink before sending the play request, fixing the issue where audio metadata was received but nothing actually played.
Queue
Each player has a built-in Queue that manages the list of upcoming tracks with three loop modes, shuffle, history navigation, and range operations.
FilterBuilder
A fluent builder for constructing Lavalink audio filters. Includes preset helpers like nightcore(), vaporwave(), and bassBoost().
RestClient
A fully typed HTTP client that wraps every Lavalink v4 REST endpoint including plugin endpoints. Accessible via node.rest.
API Reference
Manager API
Constructor
new Manager(options: ManagerOptions)| Option | Type | Default | Description |
|---|---|---|---|
| nodes | NodeOptions[] | required | Array of Lavalink node configurations |
| clientId | string | '' | Discord bot user ID (can also be set via init()) |
| clientName | string | 'dreama-link' | Identifier sent in the Client-Name header |
| shards | number | 1 | Number of shards your bot uses |
| autoPlay | boolean | true | Automatically play next track on track end |
| defaultVolume | number | 100 | Default player volume (0–1000) |
| defaultSelfDeaf | boolean | true | Whether the bot deafens itself by default when joining VC |
| defaultSelfMute | boolean | false | Whether the bot mutes itself by default when joining VC |
| defaultLoop | LoopMode | 'none' | Default loop mode for all new players |
| leaveOnEmpty | boolean | false | Automatically destroy player when voice channel becomes empty |
| leaveOnEmptyDelay | number | 30000 | Milliseconds to wait before leaving an empty channel |
| leaveOnEnd | boolean | false | Automatically destroy player when the queue ends |
| leaveOnEndDelay | number | 0 | Milliseconds to wait before leaving after queue ends |
| dave | boolean | false | Enable Discord DAVE E2EE protocol support |
| send | (guildId, payload) => void | required | Function that sends a gateway payload to Discord |
Methods
manager.init(clientId: string): ManagerInitializes the manager with the bot's user ID and connects all configured nodes.
manager.addNode(options: NodeOptions): Node
manager.removeNode(name: string): boolean
manager.getNode(name?: string): Node
manager.getBestNode(): Nodemanager.createPlayer(options: PlayerOptions): Player
manager.getPlayer(guildId: string): Player | undefined
manager.destroyPlayer(guildId: string): Promise<void>manager.search(query: string | SearchQuery, requester?: unknown): Promise<LoadResult>Searches for tracks. Pass sources array in SearchQuery to trigger LavaSearch multi-source mode.
manager.handleVoiceServerUpdate(data: VoiceServerUpdate): void
manager.handleVoiceStateUpdate(data: VoiceStateUpdate): voidMust be called from your Discord raw event handler.
manager.handleDAVEPacket(guildId: string, packet: DAVERawPacket): voidForward raw DAVE voice gateway packets for a specific guild. Requires dave: true.
manager.getDAVEState(guildId: string): DAVEState | nullReturns the current DAVE state for a guild.
manager.sendVoiceUpdate(guildId: string, channelId: string | null, options?): voidProperties
| Property | Type | Description |
|---|---|---|
| nodes | Map<string, Node> | All registered nodes |
| players | Map<string, Player> | All active players |
| connectedNodes | Node[] | Nodes that are currently connected |
| totalPlayers | number | Total number of active players |
| playingPlayers | number | Number of players currently playing |
Node API
NodeOptions
| Option | Type | Default | Description |
|---|---|---|---|
| host | string | required | Lavalink server host |
| port | number | required | Lavalink server port |
| password | string | required | Lavalink server password |
| secure | boolean | false | Use WSS/HTTPS |
| name | string | host:port | Unique identifier for this node |
| retryAmount | number | 5 | Max reconnection attempts |
| retryDelay | number | 3000 | Base delay between retries (ms) |
| resumeKey | string | '' | Enables session resuming when set |
| resumeTimeout | number | 60 | Session resume timeout in seconds |
| requestTimeout | number | 10000 | REST request timeout (ms) |
Properties
| Property | Type | Description |
|---|---|---|
| name | string | Node identifier |
| state | NodeState | 'disconnected' \| 'connecting' \| 'connected' \| 'reconnecting' \| 'destroyed' |
| connected | boolean | Shorthand for state === 'connected' |
| sessionId | string \| null | Current Lavalink session ID |
| stats | NodeStats \| null | Latest stats from Lavalink |
| info | LavalinkInfo \| null | Node info fetched on connect |
| rest | RestClient | REST API client for this node |
| activePlugins | Set<string> | Plugin names detected on this node |
| penalties | number | Computed load score used for balancing |
Methods
node.connect(): void
node.destroy(): void
node.send(data: unknown): void
node.hasPlugin(name: string): booleanPlayer API
PlayerOptions
| Option | Type | Default | Description |
|---|---|---|---|
| guildId | string | required | Discord guild ID |
| voiceChannelId | string | required | Voice channel to join |
| textChannelId | string | — | Text channel for responses |
| selfDeaf | boolean | defaultSelfDeaf | Whether the bot deafens itself |
| selfMute | boolean | defaultSelfMute | Whether the bot mutes itself |
| volume | number | defaultVolume | Initial volume |
| nodeName | string | — | Pin to a specific node by name |
Playback Methods
player.connect(): Promise<void>
player.disconnect(): Promise<void>
player.play(track?: Track, options?: PlayOptions): Promise<void>
player.pause(): Promise<void>
player.resume(): Promise<void>
player.stop(): Promise<void>
player.seek(position: number): Promise<void>
player.setVolume(volume: number): Promise<void>
player.skip(): Promise<Track | null>
player.previous(): Promise<Track | null>
player.destroy(disconnect?: boolean): Promise<void>[!NOTE]
play()automatically waits for the voice connection to be fully established with Lavalink before sending the play request. You no longer need to manually delay or pollplayer.connected.
Filter Methods
player.setFilters(filters: Filters): Promise<void>
player.applyFilterBuilder(): Promise<void>
player.clearFilters(): Promise<void>SponsorBlock Methods
Requires the sponsorblock-plugin to be installed on your Lavalink node.
player.setSponsorBlockCategories(categories: SponsorBlockCategory[]): Promise<void>
player.getSponsorBlockCategories(): Promise<SponsorBlockCategory[]>
player.clearSponsorBlock(): Promise<void>LavaLyrics Methods
Requires the lavalyrics-plugin to be installed on your Lavalink node.
player.getLyrics(skipTrackSource?: boolean): Promise<LyricsData | null>
player.getLyricsByTrack(track: Track, skipTrackSource?: boolean): Promise<LyricsData | null>Voice Readiness Methods
player.waitForVoice(timeoutMs?: number): Promise<void>
player.setVoiceReady(): voidProperties
| Property | Type | Description |
|---|---|---|
| guildId | string | Guild ID |
| voiceChannelId | string | Voice channel ID |
| textChannelId | string \| undefined | Text channel ID |
| queue | Queue | The player's queue |
| playing | boolean | Whether a track is playing |
| paused | boolean | Whether playback is paused |
| volume | number | Current volume |
| state | PlayerState | Latest player state from Lavalink |
| filters | Filters | Currently applied filters |
| filterBuilder | FilterBuilder | Filter builder instance |
| loop | LoopMode | Current loop mode |
| position | number | Current playback position in ms |
| ping | number | Latency to Discord voice server |
| connected | boolean | Whether the bot is in a voice channel |
| node | Node | The node this player is attached to |
Queue API
Methods
queue.add(track: Track | Track[], position?: number): void
queue.remove(index: number): Track | undefined
queue.removeRange(start: number, end: number): Track[]
queue.move(from: number, to: number): boolean
queue.shuffle(): void
queue.clear(): void
queue.clearHistory(): void
queue.next(): Track | null
queue.previous(): Track | null
queue.peek(): Track | null
queue.toArray(): Track[]
queue.at(index: number): Track | undefinedProperties
| Property | Type | Description |
|---|---|---|
| current | Track \| null | Currently playing track |
| loop | LoopMode | 'none' \| 'track' \| 'queue' |
| size | number | Number of tracks in the queue |
| isEmpty | boolean | Whether the queue is empty |
| totalDuration | number | Total duration of queued tracks in ms |
| history | Track[] | Previously played tracks (last 50) |
FilterBuilder API
const filters = new FilterBuilder()
.setVolume(1.0)
.setBand(0, 0.3)
.setTimescale({ speed: 1.2, pitch: 1.2 })
.setRotation({ rotationHz: 0.2 })
.build();
await player.setFilters(filters);Preset Methods
builder.nightcore(): this
builder.vaporwave(): this
builder.bassBoost(level?: number): thisManual Methods
builder.setVolume(volume: number): this
builder.setEqualizer(bands: EqualizerBand[]): this
builder.setBand(band: number, gain: number): this
builder.setKaraoke(options: KaraokeFilter): this
builder.setTimescale(options: TimescaleFilter): this
builder.setTremolo(options: TremoloFilter): this
builder.setVibrato(options: VibratoFilter): this
builder.setRotation(options: RotationFilter): this
builder.setDistortion(options: DistortionFilter): this
builder.setChannelMix(options: ChannelMixFilter): this
builder.setLowPass(options: LowPassFilter): this
builder.setPluginFilter(pluginName: string, config: object): thisClear Methods
builder.clearEqualizer(): this
builder.clearTimescale(): this
builder.clearAll(): this
builder.build(): Filters
FilterBuilder.from(filters: Filters): FilterBuilderRestClient API
Accessible via node.rest. All methods return typed Promises.
Track API
rest.loadTracks(identifier: string): Promise<LoadResult>
rest.decodeTrack(encodedTrack: string): Promise<Track>
rest.decodeTracks(encodedTracks: string[]): Promise<Track[]>Player API
rest.getPlayers(sessionId: string): Promise<LavalinkPlayer[]>
rest.getPlayer(sessionId: string, guildId: string): Promise<LavalinkPlayer>
rest.updatePlayer(sessionId, guildId, options, noReplace?): Promise<LavalinkPlayer>
rest.destroyPlayer(sessionId: string, guildId: string): Promise<void>Session API
rest.updateSession(sessionId: string, options: UpdateSessionOptions): Promise<SessionInfo>Server Info
rest.getInfo(): Promise<LavalinkInfo>
rest.getVersion(): Promise<string>
rest.getStats(): Promise<NodeStats>RoutePlanner API
rest.getRoutePlannerStatus(): Promise<RoutePlannerStatus | null>
rest.freeRoutePlannerAddress(address: string): Promise<void>
rest.freeAllRoutePlannerAddresses(): Promise<void>SponsorBlock API
rest.getSponsorBlockCategories(sessionId, guildId): Promise<SponsorBlockCategory[]>
rest.setSponsorBlockCategories(sessionId, guildId, categories): Promise<void>
rest.deleteSponsorBlockCategories(sessionId, guildId): Promise<void>LavaLyrics API
rest.getLyrics(sessionId, guildId, skipTrackSource?): Promise<LyricsData | null>
rest.getLyricsByTrack(encodedTrack, skipTrackSource?): Promise<LyricsData | null>Events
Manager Events
| Event | Arguments | Description |
|---|---|---|
| nodeReady | node, resumed | A node connected successfully |
| nodeDisconnect | node, code, reason | A node disconnected |
| nodeReconnecting | node | A node is attempting to reconnect |
| nodeError | node, error | A node encountered an error |
| nodeStats | node, stats | Node stats updated (~every 1 min) |
| nodeDestroy | node | A node was destroyed |
| trackStart | player, track | A track started playing |
| trackEnd | player, track, reason | A track ended |
| trackError | player, track, exception | A track threw an exception |
| trackStuck | player, track, thresholdMs | A track got stuck |
| queueEnd | player | Queue ran out of tracks |
| playerCreate | player | A player was created |
| playerDestroy | player | A player was destroyed |
| playerUpdate | player, state | Player state updated |
| playerPause | player | Player was paused |
| playerResume | player | Player was resumed |
| playerVolumeChange | player, oldVolume, newVolume | Player volume changed |
| voiceDisconnect | player, code, reason | Discord voice WS closed |
| socketClosed | player, code, reason, byRemote | Discord voice WS closed (with byRemote flag) |
| channelEmpty | player | Voice channel became empty (fires before auto-leave) |
| segmentsLoaded | player, segments | SponsorBlock segments loaded for track |
| segmentSkipped | player, segment | SponsorBlock segment was auto-skipped |
| chaptersLoaded | player, chapters | SponsorBlock chapters loaded for track |
| chapterStarted | player, chapter | SponsorBlock chapter started |
| lyricsLine | player, line | LavaLyrics line event (real-time sync) |
| lyricsFound | player, lyrics | LavaLyrics found lyrics for current track |
| lyricsNotFound | player | LavaLyrics could not find lyrics |
| daveReady | player, guildId | DAVE E2EE is ready for a guild |
| daveTransitionFinished | player, guildId, transitionId | DAVE MLS transition completed |
| daveError | guildId, error | DAVE protocol error |
Node Events
| Event | Arguments | Description |
|---|---|---|
| ready | node, resumed | Node connected |
| disconnect | node, code, reason | Node disconnected |
| reconnecting | node | Node is reconnecting |
| error | node, error | Node error |
| stats | node, stats | Stats received |
| destroy | node | Node destroyed |
| raw | node, message | Raw WebSocket message received |
Player Events
| Event | Arguments | Description |
|---|---|---|
| trackStart | player, track | Track started |
| trackEnd | player, track, reason | Track ended |
| trackError | player, track, exception | Track error |
| trackStuck | player, track, thresholdMs | Track stuck |
| queueEnd | player | Queue empty |
| playerUpdate | player, state | State update |
| playerPause | player | Player paused |
| playerResume | player | Player resumed |
| playerVolumeChange | player, oldVolume, newVolume | Volume changed |
| voiceDisconnect | player, code, reason | Voice WS closed |
| socketClosed | player, code, reason, byRemote | Voice WS closed with byRemote |
| segmentsLoaded | player, segments | SponsorBlock segments loaded |
| segmentSkipped | player, segment | SponsorBlock segment skipped |
| chaptersLoaded | player, chapters | SponsorBlock chapters loaded |
| chapterStarted | player, chapter | SponsorBlock chapter started |
| lyricsLine | player, line | LavaLyrics real-time line |
| lyricsFound | player, lyrics | LavaLyrics lyrics found |
| lyricsNotFound | player | LavaLyrics no lyrics found |
| destroy | player | Player destroyed |
Plugin Support
dreama-link auto-detects Lavalink plugins when a node connects by reading /v4/info. Detected plugins are printed to the console with color — green for supported plugins, yellow for unknown ones.
SponsorBlock
Plugin: sponsorblock-plugin v3.0.1+
Enables automatic segment skipping and chapter detection for YouTube tracks.
await player.setSponsorBlockCategories(['sponsor', 'selfpromo', 'interaction']);
manager.on('segmentSkipped', (player, segment) => {
console.log(`Skipped ${segment.category} from ${segment.start}s to ${segment.end}s`);
});
manager.on('chapterStarted', (player, chapter) => {
console.log(`Chapter: ${chapter.name}`);
});
manager.on('segmentsLoaded', (player, segments) => {
console.log(`Loaded ${segments.length} segments`);
});SponsorBlock categories: sponsor · selfpromo · interaction · intro · outro · preview · music_offtopic · filler
LavaLyrics
Plugin: lavalyrics-plugin v1.1.0+
Fetch synchronized or plain lyrics for any track.
const lyrics = await player.getLyrics();
if (lyrics) {
console.log(lyrics.text);
}
const lyricsByTrack = await player.getLyricsByTrack(track);
manager.on('lyricsLine', (player, line) => {
console.log(`[${line.range.start}ms] ${line.line}`);
});
manager.on('lyricsFound', (player, lyrics) => {
console.log(`Lyrics found from: ${lyrics.sourceName}`);
});LavaSrc
Plugin: lavasrc-plugin v4.4.0+
Unlocks Spotify, Apple Music, Deezer, Yandex Music, and JioSaavn as search sources.
const result = await manager.search({ source: 'spotify', query: 'Blinding Lights' });
const result2 = await manager.search({ source: 'applemusic', query: 'Bohemian Rhapsody' });
const result3 = await manager.search({ source: 'deezer', query: 'bad guy Billie Eilish' });LavaSearch
Plugin: lavasearch-plugin v1.0.0+
Search multiple sources simultaneously by passing a sources array.
const result = await manager.search({
query: 'Never Gonna Give You Up',
sources: ['youtube', 'spotify', 'deezer'],
});When sources has more than one entry, dreama-link automatically uses the mzsearch: prefix required by LavaSearch.
YouTube Source
Plugin: youtube-source v1.18.0+
Replaces the built-in YouTube source with an improved version supporting age-restricted videos and more.
const result = await manager.search({ source: 'youtube', query: 'your query' });
const result2 = await manager.search({ source: 'youtubemusic', query: 'your query' });DAVE E2EE Protocol
Discord's DAVE (Discord Audio & Video End-to-End Encryption) is an opt-in E2EE layer for voice using the MLS (Message Layer Security) protocol. Enable it in your manager options and forward DAVE voice gateway packets:
const manager = new Manager({
dave: true,
// ...
});
client.ws.on('raw', (payload) => {
if (payload.t === 'VOICE_SERVER_UPDATE') manager.handleVoiceServerUpdate(payload.d);
if (payload.t === 'VOICE_STATE_UPDATE') manager.handleVoiceStateUpdate(payload.d);
});
manager.on('daveReady', (player, guildId) => {
console.log(`DAVE E2EE is active in guild ${guildId}`);
});
manager.on('daveTransitionFinished', (player, guildId, transitionId) => {
console.log(`DAVE MLS transition ${transitionId} complete in guild ${guildId}`);
});
manager.on('daveError', (guildId, error) => {
console.error(`DAVE error in guild ${guildId}:`, error.message);
});
const state = manager.getDAVEState(guildId);
console.log(state?.ready, state?.protocolVersion);Forward raw voice gateway DAVE packets (opcodes 21–27) using:
manager.handleDAVEPacket(guildId, { op: 26, d: {} });Ready Event Compatibility
dreama-link works with all Discord.js ready event patterns. Call manager.init(client.user.id) inside your ready handler regardless of which event syntax you use:
// discord.js v14 — classic
client.once('ready', (c) => {
manager.init(c.user.id);
});
// discord.js v14 — Events enum (recommended)
client.once(Events.ClientReady, (c) => {
manager.init(c.user.id);
});
// discord.js v15 — clientReady (new name, ready is deprecated)
client.once('clientReady', (c) => {
manager.init(c.user.id);
});
// discord.js v15 — Events enum (forward-compatible)
client.once(Events.ClientReady, (c) => {
manager.init(c.user.id);
});Using Events.ClientReady from the discord.js package is the most forward-compatible approach and works across both v14 and v15.
Types Reference
type LoopMode = 'none' | 'track' | 'queue';
type TrackEndReason = 'finished' | 'loadFailed' | 'stopped' | 'replaced' | 'cleanup';
type ExceptionSeverity = 'common' | 'suspicious' | 'fault';
type NodeState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'destroyed';
type LoadResultType = 'track' | 'playlist' | 'search' | 'empty' | 'error';
type SponsorBlockCategory = 'sponsor' | 'selfpromo' | 'interaction' | 'intro' | 'outro' | 'preview' | 'music_offtopic' | 'filler';
type SearchSource = 'youtube' | 'youtubemusic' | 'soundcloud' | 'spotify' | 'applemusic' | 'deezer' | 'yandexmusic' | 'jiosaavn' | 'mzsearch' | string;Key interfaces: Track, TrackInfo, Filters, PlayerState, NodeStats, LavalinkInfo, LoadResult, LavalinkException, LyricsData, SponsorBlockSegment, SponsorBlockChapter, DAVEState.
All types are re-exported from the main package entry.
ESM & CJS Usage
ESM (TypeScript / modern Node)
import { Manager, FilterBuilder, DAVEProtocol } from 'dreama-link';CommonJS
const { Manager, FilterBuilder, DAVEProtocol } = require('dreama-link');Advanced Usage
Session Resuming
{
host: 'localhost',
port: 2333,
password: 'youshallnotpass',
resumeKey: 'my-bot-resume-key',
resumeTimeout: 60,
}Multi-Node Setup
const manager = new Manager({
nodes: [
{ host: 'node1.example.com', port: 2333, password: 'pass', name: 'us-east' },
{ host: 'node2.example.com', port: 2333, password: 'pass', name: 'eu-west', secure: true },
],
send(guildId, payload) { /* ... */ },
});
manager.createPlayer({ guildId, voiceChannelId, nodeName: 'us-east' });Auto-Disconnect
const manager = new Manager({
leaveOnEmpty: true,
leaveOnEmptyDelay: 30000,
leaveOnEnd: true,
leaveOnEndDelay: 5000,
// ...
});
manager.on('channelEmpty', (player) => {
sendMessage(player.textChannelId, 'Channel is empty — leaving in 30 seconds...');
});Filters
await player.filterBuilder
.nightcore()
.applyFilterBuilder();
await player.filterBuilder
.clearAll()
.bassBoost(0.4)
.setRotation({ rotationHz: 0.2 })
.applyFilterBuilder();Checking Active Plugins
const node = manager.getBestNode();
if (node.hasPlugin('sponsorblock')) {
await player.setSponsorBlockCategories(['sponsor', 'intro', 'outro']);
}
if (node.hasPlugin('lavalyrics')) {
const lyrics = await player.getLyrics();
}
console.log([...node.activePlugins]);Search Sources
| Source Key | Prefix | Plugin Required | Description |
|---|---|---|---|
| youtube | ytsearch: | built-in / yt-source | YouTube search |
| youtubemusic | ytmsearch: | built-in / yt-source | YouTube Music search |
| soundcloud | scsearch: | built-in | SoundCloud search |
| spotify | spsearch: | LavaSrc | Spotify search |
| applemusic | amsearch: | LavaSrc | Apple Music search |
| deezer | dzsearch: | LavaSrc | Deezer search |
| yandexmusic | ymsearch: | LavaSrc | Yandex Music search |
| jiosaavn | jssearch: | LavaSrc | JioSaavn search |
| sources[] | mzsearch: | LavaSearch | Multi-source search |
Pass a direct URL to skip the prefix entirely:
manager.search({ query: 'https://open.spotify.com/track/...' });License
MIT — see LICENSE for details.
Contributions are open! Create a Pull Request anytime you want to.
