Ryanlink is a powerful Lavalink client library designed to simplify music bot development for Discord. Built with modern TypeScript and featuring a unique architecture, it provides an intuitive interface for managing audio playback, queues, and advanced audio processing.
Ryanlink supports audio from 20+ streaming platforms through LavaSrc integration:
| Platform | Support | Platform | Support | Platform | Support |
|---|---|---|---|---|---|
| YouTube | ✓ | Spotify | ✓ | Apple Music | ✓ |
| SoundCloud | ✓ | Deezer | ✓ | Tidal | ✓ |
| Bandcamp | ✓ | Twitch | ✓ | Amazon Music | ✓ |
| Yandex Music | ✓ | JioSaavn | ✓ | Pandora | ✓ |
| Qobuz | ✓ | Audiomack | ✓ | Mixcloud | ✓ |
| Anghami | ✓ | Audius | ✓ | Gaana | ✓ |
| ✓ | Shazam | ✓ | Direct URLs | ✓ |
# npm
npm install ryanlink
# yarn
yarn add ryanlink
# pnpm
pnpm add ryanlink
# bun
bun add ryanlink
import { RyanlinkPlayer } from "npm:ryanlink@latest";
import { Client } from "discord.js";
import { RyanlinkPlayer } from "ryanlink";
const client = new Client({ intents: ["Guilds", "GuildVoiceStates"] });
const player = new RyanlinkPlayer({
nodes: [
{
name: "main",
host: "localhost",
port: 2333,
password: "youshallnotpass",
secure: false,
},
],
forwardVoiceUpdate: async (guildId, payload) => {
const guild = client.guilds.cache.get(guildId);
if (guild) guild.shard.send(payload);
},
});
client.on("ready", async () => {
await player.init(client.user.id);
console.log(`${client.user.tag} is ready!`);
});
client.on("raw", (packet) => {
player.voices.handleDispatch(packet);
});
client.login("YOUR_BOT_TOKEN");
// Play from URL or search query
const queue = await player.play("Never Gonna Give You Up", {
guildId: interaction.guildId,
voiceId: interaction.member.voice.channelId,
textId: interaction.channelId,
});
// Play from direct URL
await player.play("https://open.spotify.com/track/...", {
guildId: interaction.guildId,
voiceId: interaction.member.voice.channelId,
});
// Search with specific source
const results = await player.search("ytsearch:Rick Astley");
const queue = player.getQueue(guildId);
// Basic controls
await queue.pause();
await queue.resume();
await queue.stop();
await queue.skip();
await queue.previous();
// Volume control
await queue.setVolume(0.5); // 50%
// Repeat modes
await queue.setRepeatMode("track"); // Repeat current track
await queue.setRepeatMode("queue"); // Repeat entire queue
await queue.setRepeatMode("off"); // No repeat
// Shuffle
await queue.shuffle();
await queue.shuffle(true); // Include previous tracks
// Seek
await queue.seek(60000); // Seek to 1 minute
// Sort tracks
await queue.sortBy("duration", "desc");
await queue.sortBy("title", "asc");
await queue.sortBy((a, b) => a.duration - b.duration);
// Filter tracks
const longTracks = queue.filterTracks({
duration: { min: 180000 }, // 3+ minutes
});
const rockTracks = queue.filterTracks({
title: "rock",
});
// Find specific track
const track = queue.findTrack({ title: "Never Gonna" });
// Move tracks
await queue.move(5, 0); // Move track at index 5 to front
// Remove tracks
await queue.remove(3); // Remove track at index 3
// Get track range
const nextTen = queue.getTracks(0, 10);
// Apply EQ preset
await queue.filters.setEQPreset("BassboostHigh");
await queue.filters.setEQPreset("Rock");
await queue.filters.setEQPreset("Nightcore");
// Get available presets
const presets = queue.filters.getEQPresetNames();
// ["BassboostEarrape", "BassboostHigh", "BassboostMedium", "BassboostLow",
// "Rock", "Classic", "Pop", "Electronic", "Gaming", "Nightcore",
// "Vaporwave", "TrebleBass", "HighQuality", "BetterMusic", "FullSound",
// "Soft", "TV", "Radio", "Normalization"]
// Custom filters
await queue.filters.apply({
equalizer: [
{ band: 0, gain: 0.2 },
{ band: 1, gain: 0.15 },
],
timescale: { speed: 1.5, pitch: 1.2 },
volume: 1.0,
});
// Individual filter control
await queue.filters.set("timescale", { speed: 1.5 });
await queue.filters.delete("timescale");
await queue.filters.clear();
Automatically plays related tracks when the queue ends.
import { AutoplayExtension } from "ryanlink";
const player = new RyanlinkPlayer({
plugins: [
new AutoplayExtension({
enabled: true,
maxHistory: 50,
sources: {
spotify: true,
youtube: true,
youtubemusic: true,
soundcloud: false,
},
}),
],
});
Fetch synchronized lyrics from multiple sources.
import { LyricsExtension } from "ryanlink";
const player = new RyanlinkPlayer({
plugins: [
new LyricsExtension({
sources: ["lrclib", "genius", "musixmatch"],
cache: true,
}),
],
});
// Get lyrics
const lyrics = await queue.getLyrics();
Skip sponsored segments in YouTube videos.
import { SponsorBlockExtension } from "ryanlink";
const player = new RyanlinkPlayer({
plugins: [
new SponsorBlockExtension({
categories: ["sponsor", "intro", "outro", "selfpromo"],
autoSkip: true,
}),
],
});
Limit tracks per user for fair queue distribution.
import { FairPlayExtension } from "ryanlink";
const player = new RyanlinkPlayer({
plugins: [
new FairPlayExtension({
maxPerUser: 3,
enforceLimit: true,
}),
],
});
Save and restore queues across bot restarts.
import { PersistenceExtension } from "ryanlink";
const player = new RyanlinkPlayer({
plugins: [
new PersistenceExtension({
autoSave: true,
saveInterval: 60000, // 1 minute
}),
],
});
// Track events
player.on("trackStart", (queue, track) => {
console.log(`Now playing: ${track.title}`);
});
player.on("trackFinish", (queue, track, reason) => {
console.log(`Finished: ${track.title} (${reason})`);
});
player.on("trackError", (queue, track, error) => {
console.error(`Error: ${error.message}`);
});
player.on("trackStuck", (queue, track, threshold) => {
console.warn(`Track stuck: ${track.title}`);
});
// Queue events
player.on("queueCreate", (queue) => {
console.log(`Queue created for guild ${queue.guildId}`);
});
player.on("queueFinish", (queue) => {
console.log("Queue finished!");
});
player.on("queueDestroy", (queue, reason) => {
console.log(`Queue destroyed: ${reason}`);
});
// Node events
player.on("nodeConnect", (node, reconnects) => {
console.log(`Node ${node.name} connected`);
});
player.on("nodeReady", (node, resumed, sessionId) => {
console.log(`Node ${node.name} ready`);
});
player.on("nodeDisconnect", (node, code, reason) => {
console.log(`Node ${node.name} disconnected`);
});
player.on("nodeError", (node, error) => {
console.error(`Node error: ${error.message}`);
});
Ryanlink is built with TypeScript and provides complete type definitions.
Extend types for custom data:
declare module "ryanlink" {
interface QueueContext {
textChannelId: string;
requesterId: string;
}
interface CommonUserData {
id: string;
username: string;
requestedAt: number;
}
interface CommonPluginInfo {
customField?: string;
}
}
Ryanlink uses a feature-based architecture for better organization:
src/
├── core/ # Player and plugin system
├── lavalink/ # Lavalink connection and HTTP client
├── audio/ # Queue, tracks, and filters
├── voice/ # Voice connection management
├── extensions/ # Built-in extensions
├── config/ # Configuration and constants
├── utils/ # Utility functions
└── types/ # TypeScript definitions
Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Ryanlink was inspired by and builds upon concepts from: