socket-dave
v1.1.1
Published
Standalone Lavalink v3/v4 client with Discord DAVE (E2EE) channelId voice support. Zero external runtime dependencies.
Downloads
541
Maintainers
Readme
socket-dave
A fully standalone Lavalink v3/v4 client for Discord bots with built-in DAVE (E2EE) support.
Zero runtime dependencies beyond ws (used as a fallback for Bun).
Why socket-dave?
Discord's DAVE protocol (rolled out in 2024) adds end-to-end encryption to voice channels. When a Lavalink bot joins a voice channel, Discord now requires the channelId (voice channel snowflake) to be included in the voice payload sent back to Lavalink during E2EE key negotiation.
Without channelId, DAVE-enabled servers drop audio after the first key renegotiation — usually manifesting as:
- Silent audio ~30–60s after joining
WebSocketClosedEventcode4014on channel moves- Bots that play fine in private servers but fail in larger ones
This package fixes that at the source.
Installation
npm install socket-dave
# or
yarn add socket-dave
# or
pnpm add socket-daveNo other packages required. socket-dave is fully self-contained.
Quick start
import { SocketDave, Library } from 'socket-dave';
import { Client, GatewayIntentBits } from 'discord.js';
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildMessages,
],
});
const manager = new SocketDave({
library: new Library.DiscordJS(client),
nodes: [
{
name: 'main',
host: 'localhost',
port: 2333,
auth: 'youshallnotpass',
secure: false,
},
],
options: {
defaultSearchEngine: 'youtube',
defaultVolume: 100,
resume: false,
},
});
manager.on('nodeConnect', (node) => console.log(`Node connected: ${node.options.name}`));
manager.on('trackStart', (player, track) => console.log(`Now playing: ${track.title}`));
manager.on('queueEmpty', (player) => player.disconnect());
client.on('interactionCreate', async (interaction) => {
if (!interaction.isChatInputCommand()) return;
if (interaction.commandName === 'play') {
const query = interaction.options.getString('query', true);
const vc = (interaction.member as any)?.voice?.channel;
if (!vc) return interaction.reply('Join a voice channel first!');
const player = await manager.create({
guildId: interaction.guildId!,
voiceId: vc.id,
textId: interaction.channelId,
shardId: 0,
deaf: true,
});
const result = await manager.search(query, { requester: interaction.user });
if (!result.tracks.length) return interaction.reply('No results found.');
if (result.type === 'PLAYLIST') {
player.queue.add(result.tracks);
await interaction.reply(`Queued playlist: **${result.playlistName}** (${result.tracks.length} tracks)`);
} else {
player.queue.add(result.tracks[0]);
await interaction.reply(`Queued: **${result.tracks[0].title}**`);
}
if (!player.playing) await player.play();
}
});
client.login(process.env.DISCORD_TOKEN);DAVE internals
The DAVE fix is in two places:
Voice.buildDavePayload()
Every time voice state changes, this assembles the payload sent to Lavalink:
{
token: serverUpdate.token,
endpoint: serverUpdate.endpoint,
sessionId: sessionId,
channelId: voiceId, // ← the DAVE addition
}Player.sendServerUpdate()
Called on connect and on every connectionUpdate event, it calls Voice.buildDavePayload() and sends the result to Lavalink's REST API via PATCH /sessions/:id/players/:guildId.
Player.setVoiceChannel()
After moving to a new channel, connect() resolves and immediately calls sendServerUpdate() so Lavalink receives the updated channelId without waiting for the next Discord event.
Supported library connectors
| Library | Import |
|---------|--------|
| discord.js | new Library.DiscordJS(client) |
| Eris | new Library.ErisJS(client) |
| Oceanic.js | new Library.OceanicJS(client) |
| Seyfert | new Library.Seyfert(client) |
Supported Lavalink driver
| Driver | ID |
|--------|----|
| Lavalink v4 | lavalink/v4/koinu |
Lavalink v3 and Nodelink are not supported. Use a Lavalink v4 server.
Custom drivers
Extend AbstractDriver and register it via additionalDriver:
import { AbstractDriver } from 'socket-dave';
class MyDriver extends AbstractDriver {
id = 'mydriver/v1';
// ... implement abstract methods
}
const manager = new SocketDave({
// ...
options: { additionalDriver: [MyDriver] },
});Lavalink version requirement
Your Lavalink server should be v4.0.8+ for DAVE support. Older servers silently ignore the channelId field — no breakage, just no DAVE participation.
License
MIT
