A powerful and efficient Lavalink client for Discord.js, designed for high performance and ease of use.
High Performance
Optimized for speed and efficiency
Easy Integration
Seamless Discord.js integration
Reliable
Built for stability and resilience
Installation
npm install aqualink
Basic Setup
Quick example of setting up AquaLink with Discord.js:
const { Client } = require('discord.js');
const { Aqua } = require('aqualink');
const client = new Client({
intents: ['Guilds', 'GuildVoiceStates']
});
client.aqua = new Aqua(client, nodes, {
nodes: [{
host: 'localhost',
port: 2333,
password: 'youshallnotpass'
}],
send: (guildId, payload) => {
const guild = client.guilds.cache.get(guildId);
if (guild) guild.shard.send(payload);
}
});
Initialization
Create an initialization file to set up AquaLink events:
const { client_id } = require("../../../config.js"); // change to where your client_id is located.
const { logger } = require("../../../utils/logger.js"); // change to what you have for logging.
const client = require("../../../index.js"); // change the location of where your index/bot.js is located.
module.exports = async () => {
client.aqua.init(client_id)
logger(`Successfully Initiated Aqualink Events`, "debug");
};
Make sure to have your client ID in the config file and a logger utility set up for proper initialization.
Quick Start
Here's a simple example of playing music:
client.on('interactionCreate', async interaction => {
if (!interaction.isCommand()) return;
if (interaction.commandName === 'play') {
const query = interaction.options.getString('song');
const voiceChannel = interaction.member.voice.channel;
if (!voiceChannel) {
return interaction.reply('You need to be in a voice channel!');
}
try {
const player = aqua.createPlayer({
guildId: interaction.guildId,
voiceChannel: voiceChannel.id,
textChannel: interaction.channelId
});
const result = await aqua.search(query);
if (!result.tracks.length) {
return interaction.reply('No results found!');
}
player.queue.add(result.tracks[0]);
if (!player.playing) await player.play();
await interaction.reply(`Added ${result.tracks[0].title} to the queue!`);
} catch (error) {
console.error(error);
await interaction.reply('An error occurred!');
}
}
});
Make sure to have your client ID in the config file and a logger utility set up for proper initialization. This example assumes you have already set up slash commands in your Discord application.
AquaLink Class
The main class for interacting with Lavalink.
Constructor
new Aqua(client, options)
Creates a new AquaLink instance.
const aqua = new Aqua(client, {
nodes: [{
host: 'localhost',
port: 2333,
password: 'youshallnotpass',
secure: false
}],
defaultSearchPlatform: 'youtube'
});
Methods
.createPlayer(options)
Creates a new player instance for a guild.
const player = aqua.createPlayer({
guildId: '123456789',
voiceChannel: '987654321',
textChannel: '123789456'
});
.search(query, options)
Searches for tracks using the specified query.
const results = await aqua.search('never gonna give you up');
Player Class
Handles music playback and track management.
Methods
.play()
Starts playing the current track in the queue.
await player.play();
.pause() / .resume()
Controls playback state.
player.pause(true);
player.pause(false);
.stop()
Stops the current track and clears the queue.
player.stop();
Playing a Track
Here's how to play a track using AquaLink, including automatic player creation if needed:
async function playTrack(guildId, track) {
let player = client.aqua.players.get(interaction.guildId);
if (!player) {
player = client.aqua.createConnection({
defaultVolume: 65,
guildId: interaction.guildId,
voiceChannel: interaction.member.voice.channelId,
textChannel: interaction.channelId,
deaf: true
});
}
player.play(track);
}
This function checks for an existing player and creates one if needed. The player is configured with default settings including volume level and automatic deafening of the bot.
Parameters
guildId
- The ID of the guild (server)track
- The track object to play
Player Options
defaultVolume
- Initial volume level (0-100)guildId
- The guild ID for the connectionvoiceChannel
- The voice channel ID to connect totextChannel
- The text channel ID for notificationsdeaf
- Whether the bot should be deafened
Example of Searching for a Track Using Lavalink
Here's how to search for tracks and add them to the queue using AquaLink's resolve method:
async function searchAndPlay(guildId, query) {
const resolve = await client.aqua.resolve({
query,
requester: interaction.member, // optional!
source: "scsearch"
});
if (!results.tracks.length) return console.log("No results found.");
const track = results.tracks[0];
await player.queue.add(track)
if (!player.playing && !player.paused && player.queue.size > 0) {
player.play();
}
}
The resolve method supports different search sources like 'ytsearch' (YouTube), 'scsearch' (SoundCloud), and direct URLs. The requester field is optional but useful for tracking who requested each track.
Parameters
guildId
- The ID of the guild (server)query
- The search query or URL
Resolve Options
query
- The search term or URLrequester
- The user who requested the track (optional)source
- The search source (ytsearch, scsearch, etc.)
Supported Sources
ytsearch
- YouTube searchscsearch
- SoundCloud searchdzsearch
- Deezer search- Direct URLs from supported platforms
Controlling Playback
Here are various methods to control music playback using AquaLink:
Pausing and Resuming
async function pausePlayback(guildId) {
const player = client.aqua.players.get(interaction.guildId);
if (player) player.pause(true);
}
async function resumePlayback(guildId) {
const player = client.aqua.players.get(interaction.guildId);
if (player) player.pause(false);
}
Skipping a Track
async function skipTrack(guildId) {
const player = client.aqua.players.get(interaction.guildId);
if (player) player.stop();
}
Stopping and Disconnecting
async function stopPlayback(guildId) {
const player = client.aqua.players.get(interaction.guildId);
if (player) player.stop();
}
async function disconnectPlayer(guildId) {
const player = client.aqua.players.get(interaction.guildId);
if (player) player.destroy();
}
All these functions first check if a player exists for the guild before attempting to control playback. The destroy() method will completely remove the player instance and disconnect from the voice channel.
Available Methods
pause(boolean)
- Pause or resume playbackstop()
- Stop current track and move to next in queuedestroy()
- Disconnect and cleanup player instance
Parameters
guildId
- The ID of the guild (server)pause(true/false)
- True to pause, false to resume
Queue Management
Here are methods to manage the track queue in AquaLink:
Adding a Track to the Queue
async function addToQueue(guildId, track) {
const player = client.aqua.players.get(interaction.guildId);
if (!player.queue) player.queue = [];
player.queue.push(track);
if (!player.playing) playTrack(guildId, player.queue.shift());
}
Viewing the Current Queue
async function viewQueue(guildId) {
const player = client.aqua.players.get(interaction.guildId);
if (!player || !player.queue.length) return console.log("The queue is empty.");
console.log("Current Queue:");
player.queue.forEach((track, index) => {
console.log(`${index + 1}. ${track.info.title}`);
});
}
Looping a Track
async function loopTrack(guildId) {
const player = client.aqua.players.get(interaction.guildId);
if (!player) return;
player.queue.unshift(player.queue[player.queue.length - 1]);
}
Queue management functions handle the playlist of tracks. The queue is maintained per guild, and tracks are automatically played in sequence. The loop function adds the current track back to the beginning of the queue for continuous playback.
Queue Methods
push(track)
- Add a track to the end of queueshift()
- Remove and return the first trackunshift(track)
- Add a track to the beginningforEach(callback)
- Iterate through the queue
Track Properties
track.info.title
- The track's titletrack.info.author
- The track's authortrack.info.length
- Duration in millisecondstrack.info.uri
- Source URL of the track
Disconnecting and Cleanup
To ensure your bot properly disconnects and cleans up resources, use the following:
async function cleanup(guildId) {
const player = client.aqua.players.get(interaction.guildId);
if (player) {
player.stop();
player.destroy();
}
}
Always ensure proper cleanup when disconnecting your bot to prevent resource leaks and hanging voice connections. This is especially important when handling bot shutdowns or voice channel leave events.
Cleanup Steps
player.stop()
- Stops any current playbackplayer.destroy()
- Destroys the player instance and disconnects from voice
Usage Examples
- Bot shutdown events
- Voice channel leave events
- Guild leave events
- Manual disconnect commands
Handling Voice State Updates
AquaLink requires voice state updates to maintain connections. Ensure you handle these events properly:
client.on("raw", (d) => {
if (![GatewayDispatchEvents.VoiceStateUpdate, GatewayDispatchEvents.VoiceServerUpdate].includes(d.t)) return;
client.aqua.updateVoiceState(d);
});
This event handler is crucial for maintaining stable voice connections. It processes voice state and server updates from Discord and passes them to AquaLink for proper connection management.
Connecting to a Voice Channel
Here's how to connect your bot to a voice channel using AquaLink:
async function connectToChannel(guildId, channelId) {
const player = client.aqua.players.get(interaction.guildId);
player.connect(channelId);
}
The player instance handles the voice connection. Make sure you have a valid player instance before attempting to connect, and ensure the bot has the necessary permissions in the voice channel.
Parameters
guildId
- The ID of the guild (server)channelId
- The ID of the voice channel to connect to
Queue Class
Manages the track queue system.
Methods
.add(track)
Adds a track to the queue.
player.queue.add(track);
.remove(index)
Removes a track from the queue.
player.queue.remove(0);
.shuffle()
Shuffles the current queue.
player.queue.shuffle();
Node Class
Manages Lavalink node connections.
Properties
.stats
Current node statistics.
console.log(node.stats);
Audio Filters
Apply audio effects and filters to your music.
Equalizer
player.filters.setTremolo(true); ex: ({
equalizer: [
{ band: 0, gain: 0.6 },
{ band: 1, gain: 0.7 },
{ band: 2, gain: 0.8 }
]
});
Karaoke
player.setFilter({
karaoke: {
level: 1.0,
monoLevel: 1.0,
filterBand: 220.0,
filterWidth: 100.0
}
});
Events
Handle various player and track events.
trackStart
client.aqua.on('trackStart', (track) => {
console.log(`Now playing: ${track.info.title}`);
});
queueEnd
client.aqua.on('queueEnd', () => {
console.log('Queue has ended!');
});
Events
Handle various node, player, and track events in AquaLink.
Node Events
Events related to Lavalink node connections.
// Node connection event ex: const client = require("../../../index.js");
client.aqua.on('nodeConnect', (node) => {
console.log(`Node ${node.host} connected`);
});
client.aqua.on('nodeDisconnect', (node, reason) => {
console.log(`Node disconnected: ${reason}`);
});
client.aqua.on('nodeError', (node, error) => {
console.error(`Node ${node.host} had an error: ${error.message}`);
});
Player Events
Events related to player lifecycle.
// the client is your index.js ex: const client = require("../../../index.js");
client.aqua.on('playerCreate', (player) => {
console.log(`Player created in guild: ${player.guildId}`);
});
client.aqua.on('playerDestroy', (player) => {
console.log(`Player destroyed in guild: ${player.guildId}`);
});
client.aqua.on('queueEnd', (player) => {
console.log(`Queue ended in guild: ${player.guildId}`);
setTimeout(() => {
if (!player.playing) player.disconnect();
}, 300000);
});
Track Events
Events related to track playback.
client.aqua.on('trackStart', (track) => {
console.log(`Now playing: ${track.title}`);
});
client.aqua.on('trackEnd', (track, reason) => {
console.log(`Track ${track.title} ended: ${reason}`);
});
client.aqua.on('trackError', (track, error) => {
console.error(`Error playing ${track.title}: ${error.message}`);
});
client.aqua.on('trackStuck', (track, threshold) => {
console.warn(`Track ${track.title} got stuck (threshold: ${threshold}ms)`);
player.skip();
});
Troubleshooting
Common issues and their solutions when working with AquaLink:
No Sound or Bot Not Joining
- ✓ Ensure the Lavalink server is running and accessible.
- ✓ Verify the Lavalink password and port are correct in your code.
- ✓ Ensure your bot has the correct permissions (CONNECT and SPEAK).
// Example permissions check
if (!voiceChannel.permissionsFor(client.user).has(['CONNECT', 'SPEAK'])) {
return console.log('Missing required voice permissions!');
}
Bot Leaves the Channel Unexpectedly
- ✓ The bot may be idle for too long. Consider implementing an inactivity timeout.
- ✓ Lavalink may be crashing—check your Lavalink logs.
// Example inactivity timeout
client.on('queueEnd', (player) => {
setTimeout(() => {
if (!player.playing) player.destroy();
}, 300000); // 5 minutes
});
Music Skipping or Lagging
- ✓ Check your server's CPU and memory usage. Lavalink requires adequate resources.
- ✓ If hosting Lavalink remotely, ensure the network connection is stable.
Recommended minimum specs for Lavalink: 2 CPU cores, 512MB RAM
If issues persist, check the Lavalink server logs for detailed error messages. You can also enable debug mode in AquaLink for more detailed logging:
client.aqua = new Aqua(client, nodes, {
debug: true,
// ... other options
});
Common Error Codes
ECONNREFUSED
- Lavalink server not accessibleAuthenticationFailed
- Incorrect Lavalink passwordTrackStuck
- Track playback issuesLoadFailed
- Unable to load track
Required Permissions
CONNECT
- Join voice channelsSPEAK
- Play audio in voice channelsVIEW_CHANNEL
- See the voice channelUSE_VAD
- Use voice activity
Error Handling
Handle and manage errors effectively.
Error Events
client.aqua.on('error', (error) => {
console.error('Player error:', error);
});
client.aqua.on('nodeError', (node, error) => {
console.error(`Node ${node.host} error:`, error);
});