Skip to content

Quick Setup

The RyanlinkManager is the central class in Ryanlink. It manages Lavalink nodes, creates and destroys audio players, and handles Discord voice state updates. It extends Node’s EventEmitter.

  1. Create the manager

    import { Client, GatewayIntentBits } from 'discord.js';
    import { RyanlinkManager } from 'ryanlink';
    const client = new Client({
    intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
    });
    const manager = new RyanlinkManager({
    nodes: [{
    id: 'main',
    host: 'localhost',
    port: 2333,
    authorization: 'youshallnotpass',
    autoChecks: { pluginValidations: false, sourcesValidations: false },
    }],
    sendToShard: (guildId, payload) => {
    const totalShards = client.ws.shards.size || 1;
    const shardId = Number((BigInt(guildId) >> 22n) % BigInt(totalShards));
    const shard = client.ws.shards.get(shardId);
    if (shard) shard.send(payload);
    else client.guilds.cache.get(guildId)?.shard?.send(payload);
    },
    client: { id: process.env.CLIENT_ID! },
    resuming: { enabled: true, timeout: 60000 },
    });
  2. Forward voice events

    client.on('raw', (packet) => {
    if (manager.initiated) {
    manager.provideVoiceUpdate(packet).catch(() => {});
    }
    });
  3. Initialize on ready

    client.once('ready', async () => {
    await manager.init({ id: client.user!.id, username: client.user!.username });
    console.log('Ryanlink ready');
    });
  4. Play a track

    // In your slash command handler
    const player = manager.createPlayer({
    guildId: interaction.guildId!,
    voiceChannelId: interaction.member.voice.channelId!,
    textChannelId: interaction.channelId,
    selfDeaf: true,
    });
    if (!player.connected) await player.connect();
    const result = await manager.search(
    { query: 'never gonna give you up', source: 'ytsearch' },
    interaction.user
    );
    if (!result.tracks.length) {
    return interaction.reply('No results found.');
    }
    player.queue.add(result.tracks[0]);
    if (!player.playing) await player.play();

new RyanlinkManager(options: RyanConfiguration)

Required options:

  • nodes — array of node configurations (can be empty [])
  • sendToShard — function to send gateway payloads to Discord
  • client.id — your bot’s user ID
PropertyTypeDescription
optionsRyanConfigurationApplied configuration with defaults
nodeManagerNodeManagerManages all Lavalink nodes
utilsRyanlinkUtilsInternal utility functions
playersMiniMap<string, Player>Active players keyed by guildId
voiceStatesMap<string, VoiceStateEntry>Cached Discord voice states
initiatedbooleanWhether init() has been called
useablebooleantrue if at least one node is connected

Connects to all configured Lavalink nodes. Call once after the bot is ready.

await manager.init({ id: client.user.id, username: client.user.username });

Creates a new player for a guild, or returns the existing one.

const player = manager.createPlayer({ guildId: '...', voiceChannelId: '...' });

Returns the player for a guild, or undefined.

const player = manager.getPlayer('123456789');

Destroys a player — disconnects from voice, cleans up resources, emits playerDestroy. This is the correct way to stop a player.

await manager.destroyPlayer('123456789', 'User left');

Removes a player from the internal map without full cleanup. Use destroyPlayer instead unless you know what you’re doing. Throws if the player is still connected and dontThrowError is not set.

manager.search(query, requestUser?, node?, throwOnEmpty?)

Section titled “manager.search(query, requestUser?, node?, throwOnEmpty?)”

Searches for tracks on the least-used connected node (or a specific node).

const result = await manager.search({ query: 'daft punk', source: 'spsearch' }, user);
// result.loadType — 'search' | 'track' | 'playlist' | 'empty' | 'error'
// result.tracks — Track[]
// result.playlist — PlaylistInfo | null

Handles raw Discord gateway payloads. Forward all VOICE_STATE_UPDATE, VOICE_SERVER_UPDATE, and CHANNEL_DELETE events here.

client.on('raw', (packet) => manager.provideVoiceUpdate(packet));

manager.getVoiceStateUsers(guildId, channelId)

Section titled “manager.getVoiceStateUsers(guildId, channelId)”

Returns cached voice state entries for users in a specific channel.

const users = manager.getVoiceStateUsers('guildId', 'channelId');

Returns { initiated, playerCount, nodeCount }.


// Play (uses queue.current or shifts from queue.tracks)
await player.play();
// Pause / resume
await player.pause(true); // pause
await player.pause(false); // resume
await player.resume(); // same as pause(false), throws if not paused
// Seek (ms)
await player.seek(30000);
// Skip
await player.skip(); // skip current track
await player.skip(2); // skip 2 tracks ahead
// Stop
await player.stopPlaying(clearQueue: true, executeAutoplay: false);
// Volume (0–1000)
await player.setVolume(80);
// Repeat
await player.setRepeatMode('off' | 'track' | 'queue');
await player.connect();
await player.disconnect();
await player.setVoiceChannel({ voiceChannelId: 'newChannelId' });
player.set('myKey', value);
player.get<T>('myKey');
player.setData('myKey', value);
player.getData<T>('myKey');
player.deleteData('myKey');
player.clearData();
player.getAllData();
PropertyTypeDescription
guildIdstringGuild ID
voiceChannelIdstring | nullCurrent voice channel
textChannelIdstring | nullText channel for messages
playingbooleanWhether audio is playing
pausedbooleanWhether audio is paused
volumenumberCurrent volume (0–1000)
internalVolumenumberVolume after volumeDecrementer
positionnumberCurrent playback position (ms, computed)
repeatMode'off' | 'track' | 'queue'Repeat mode
autoplaybooleanWhether autoplay is enabled
queueQueueThe player’s queue
filterManagerFilterManagerFilter controls
nodeRyanlinkNode | NodeLinkNodeThe assigned audio node
ping{ node: number, ws: number }Latency metrics
recentHistorystring[]Recent track identifiers (for autoplay)

const q = player.queue;
// Add tracks (returns new queue length)
q.add(track);
q.add(track, 0); // insert at position 0 (play next)
q.add([track1, track2]); // add multiple
// Remove
q.remove(index); // remove 1 track at index
q.remove(index, 3); // remove 3 tracks starting at index
// Reorder
q.move(fromIndex, toIndex);
q.shuffle();
// Clear
q.clear(); // clear tracks only
q.clear(true); // clear tracks + previous
// Skip to
q.skipTo(index); // shift to track at index, set as current
// Inspect
q.size; // number of queued tracks
q.first; // first track or null
q.last; // last track or null
q.totalDuration; // total duration including current (ms)
q.current; // currently playing track
q.previous; // previously played tracks (up to maxPreviousTracks)
// Functional
q.find(predicate);
q.filter(predicate);
q.map(callback);
q.some(predicate);
q.every(predicate);

const fm = player.filterManager;
// High-level presets
await fm.toggleNightcore();
await fm.toggleVaporwave();
await fm.setSpeed(1.2);
await fm.setPitch(1.1);
await fm.setVolume(0.8); // filter volume (0.0–5.0)
// EQ
await fm.setEQ([{ band: 0, gain: 0.5 }, { band: 1, gain: 0.3 }]);
await fm.setPreset('BassboostHigh');
await fm.setPreset('Nightcore');
await fm.setPreset('Vaporwave');
await fm.setPreset('Clear');
// Low-level
await fm.set('rotation', { rotationHz: 0.2 });
await fm.remove('rotation');
await fm.override({ timescale: { speed: 1.1, pitch: 1.2, rate: 1 } });
// Reset everything
await fm.resetFilters();
await fm.clear(); // alias for resetFilters
// Check state
fm.filters.nightcore; // boolean
fm.filters.vaporwave; // boolean
fm.filters.rotation; // boolean
fm.checkFiltersState(); // recompute filter flags

// Direct URL
const result = await manager.search({ query: 'https://open.spotify.com/track/...' }, user);
// Search with prefix
const result2 = await manager.search({ query: 'spsearch:daft punk' }, user);
// Add all results from a playlist
if (result.loadType === 'playlist') {
player.queue.add(result.tracks);
} else {
player.queue.add(result.tracks[0]);
}
if (!player.playing) await player.play();
// Get least-used nodes
const nodes = manager.nodeManager.leastUsedNodes('weighted');
// Create a node at runtime
manager.nodeManager.createNode({ id: 'extra', host: 'node2.example.com', port: 2333, authorization: 'pw' });
// Delete a node (moves players if movePlayers=true)
manager.nodeManager.deleteNode('extra', true);
// Disconnect/reconnect all
await manager.nodeManager.disconnectAll();
await manager.nodeManager.connectAll();
await manager.nodeManager.reconnectAll();
await player.setSponsorBlock(['sponsor', 'selfpromo', 'intro', 'outro']);
const segments = await player.getSponsorBlock();
await player.deleteSponsorBlock();
// Via node (lavalyrics-plugin)
const lyrics = await player.node.lyrics.get(track);
const current = await player.node.lyrics.getCurrent(player.guildId);
await player.node.lyrics.subscribe(player.guildId);
await player.node.lyrics.unsubscribe(player.guildId);
// Convenience wrappers on player
const lyrics2 = await player.getLyrics(track);
const current2 = await player.getCurrentLyrics();
player.subscribeLyrics();
player.unsubscribeLyrics();