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.
Minimal Setup (Discord.js)
Section titled “Minimal Setup (Discord.js)”-
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 },}); -
Forward voice events
client.on('raw', (packet) => {if (manager.initiated) {manager.provideVoiceUpdate(packet).catch(() => {});}}); -
Initialize on ready
client.once('ready', async () => {await manager.init({ id: client.user!.id, username: client.user!.username });console.log('Ryanlink ready');}); -
Play a track
// In your slash command handlerconst 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();
Manager API
Section titled “Manager API”Constructor
Section titled “Constructor”new RyanlinkManager(options: RyanConfiguration)Required options:
nodes— array of node configurations (can be empty[])sendToShard— function to send gateway payloads to Discordclient.id— your bot’s user ID
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
options | RyanConfiguration | Applied configuration with defaults |
nodeManager | NodeManager | Manages all Lavalink nodes |
utils | RyanlinkUtils | Internal utility functions |
players | MiniMap<string, Player> | Active players keyed by guildId |
voiceStates | Map<string, VoiceStateEntry> | Cached Discord voice states |
initiated | boolean | Whether init() has been called |
useable | boolean | true if at least one node is connected |
Methods
Section titled “Methods”manager.init(clientData)
Section titled “manager.init(clientData)”Connects to all configured Lavalink nodes. Call once after the bot is ready.
await manager.init({ id: client.user.id, username: client.user.username });manager.createPlayer(options)
Section titled “manager.createPlayer(options)”Creates a new player for a guild, or returns the existing one.
const player = manager.createPlayer({ guildId: '...', voiceChannelId: '...' });manager.getPlayer(guildId)
Section titled “manager.getPlayer(guildId)”Returns the player for a guild, or undefined.
const player = manager.getPlayer('123456789');manager.destroyPlayer(guildId, reason?)
Section titled “manager.destroyPlayer(guildId, reason?)”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');manager.deletePlayer(guildId)
Section titled “manager.deletePlayer(guildId)”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 | nullmanager.provideVoiceUpdate(data)
Section titled “manager.provideVoiceUpdate(data)”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');manager.toJSON()
Section titled “manager.toJSON()”Returns { initiated, playerCount, nodeCount }.
Player API
Section titled “Player API”Playback
Section titled “Playback”// Play (uses queue.current or shifts from queue.tracks)await player.play();
// Pause / resumeawait player.pause(true); // pauseawait player.pause(false); // resumeawait player.resume(); // same as pause(false), throws if not paused
// Seek (ms)await player.seek(30000);
// Skipawait player.skip(); // skip current trackawait player.skip(2); // skip 2 tracks ahead
// Stopawait player.stopPlaying(clearQueue: true, executeAutoplay: false);
// Volume (0–1000)await player.setVolume(80);
// Repeatawait player.setRepeatMode('off' | 'track' | 'queue');await player.connect();await player.disconnect();await player.setVoiceChannel({ voiceChannelId: 'newChannelId' });Data Store
Section titled “Data Store”player.set('myKey', value);player.get<T>('myKey');player.setData('myKey', value);player.getData<T>('myKey');player.deleteData('myKey');player.clearData();player.getAllData();Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
guildId | string | Guild ID |
voiceChannelId | string | null | Current voice channel |
textChannelId | string | null | Text channel for messages |
playing | boolean | Whether audio is playing |
paused | boolean | Whether audio is paused |
volume | number | Current volume (0–1000) |
internalVolume | number | Volume after volumeDecrementer |
position | number | Current playback position (ms, computed) |
repeatMode | 'off' | 'track' | 'queue' | Repeat mode |
autoplay | boolean | Whether autoplay is enabled |
queue | Queue | The player’s queue |
filterManager | FilterManager | Filter controls |
node | RyanlinkNode | NodeLinkNode | The assigned audio node |
ping | { node: number, ws: number } | Latency metrics |
recentHistory | string[] | Recent track identifiers (for autoplay) |
Queue API
Section titled “Queue API”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
// Removeq.remove(index); // remove 1 track at indexq.remove(index, 3); // remove 3 tracks starting at index
// Reorderq.move(fromIndex, toIndex);q.shuffle();
// Clearq.clear(); // clear tracks onlyq.clear(true); // clear tracks + previous
// Skip toq.skipTo(index); // shift to track at index, set as current
// Inspectq.size; // number of queued tracksq.first; // first track or nullq.last; // last track or nullq.totalDuration; // total duration including current (ms)q.current; // currently playing trackq.previous; // previously played tracks (up to maxPreviousTracks)
// Functionalq.find(predicate);q.filter(predicate);q.map(callback);q.some(predicate);q.every(predicate);Filters
Section titled “Filters”const fm = player.filterManager;
// High-level presetsawait 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)
// EQawait 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-levelawait fm.set('rotation', { rotationHz: 0.2 });await fm.remove('rotation');await fm.override({ timescale: { speed: 1.1, pitch: 1.2, rate: 1 } });
// Reset everythingawait fm.resetFilters();await fm.clear(); // alias for resetFilters
// Check statefm.filters.nightcore; // booleanfm.filters.vaporwave; // booleanfm.filters.rotation; // booleanfm.checkFiltersState(); // recompute filter flagsKey Patterns
Section titled “Key Patterns”Searching and Playing
Section titled “Searching and Playing”// Direct URLconst result = await manager.search({ query: 'https://open.spotify.com/track/...' }, user);
// Search with prefixconst result2 = await manager.search({ query: 'spsearch:daft punk' }, user);
// Add all results from a playlistif (result.loadType === 'playlist') { player.queue.add(result.tracks);} else { player.queue.add(result.tracks[0]);}
if (!player.playing) await player.play();Node Management
Section titled “Node Management”// Get least-used nodesconst nodes = manager.nodeManager.leastUsedNodes('weighted');
// Create a node at runtimemanager.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 allawait manager.nodeManager.disconnectAll();await manager.nodeManager.connectAll();await manager.nodeManager.reconnectAll();SponsorBlock
Section titled “SponsorBlock”await player.setSponsorBlock(['sponsor', 'selfpromo', 'intro', 'outro']);const segments = await player.getSponsorBlock();await player.deleteSponsorBlock();Lyrics
Section titled “Lyrics”// 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 playerconst lyrics2 = await player.getLyrics(track);const current2 = await player.getCurrentLyrics();player.subscribeLyrics();player.unsubscribeLyrics();