tunelink
v0.1.8
Published
A high-performance, modular, and easy-to-use Lavalink client for Node.js, featuring advanced player and queue management, robust auto-resume, and dynamic node failover.
Maintainers
Readme
📚 Table of Contents
- Why TuneLink?
- Features
- Supported Platforms
- Installation
- Usage
- Project Structure
- Showcase
- Requirements
- Contributing
- Support & Community
- License
🤔 Why TuneLink?
Created and maintained by Ryuzii — for developers who want total control, performance, and reliability in their Discord music bots.
- No bloat, no legacy code, no global state. Just pure, modern, event-driven Lavalink client magic.
- Designed for advanced bots, but easy enough for beginners.
- Handles all the hard stuff: node failover, auto-resume, queue management, and more.
- You want the best? You use TuneLink.
📢 Main Features
- ⚡ High Performance: Minimal RAM/CPU usage, fast event-driven core.
- 🧩 Modular: Clean separation of player, queue, node, REST, and connection logic.
- 🎶 Advanced Player & Queue: Powerful queue, metadata, search, and playback control.
- 🔄 Robust Auto-Resume: Seamless player state recovery after restarts/disconnects, with a dedicated
autoResumeevent for precise event handling (no duplicatetrackStart). - 🔀 Dynamic Node Failover: Automatic node selection and failover for reliability.
- 🎵 Multi-Source Support: YouTube, SoundCloud, Spotify, and more (via Lavalink plugins).
- 🛠️ Easy API: Simple, expressive API for rapid bot development.
- 🛡️ Production Ready: Handles edge cases, errors, and reconnections gracefully.
🎵 Supported Platforms
- YouTube & YouTube Music
- SoundCloud
- Spotify (LavaSrc)
- Apple Music (LavaSrc)
- Deezer (LavaSrc)
- And more!
📦 Installation
npm install tunelink🚀 Usage
Full Example (Discord.js v14+)
const { Client, GatewayIntentBits } = require('discord.js');
const { TuneLink } = require('tunelink');
const LAVALINK_NODES = [
{
host: 'lavalink.devxcode.in',
port: 443,
password: 'DevamOP',
name: 'Local',
secure: true
},
];
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent,
],
});
const music = new TuneLink(client, LAVALINK_NODES, {
send: (payload) => {
const guild = client.guilds.cache.get(payload.d.guild_id);
if (guild) guild.shard.send(payload);
},
restVersion: 'v4',
defaultSource: 'ytm',
autoPause: { enabled: true },
autoResume: { enabled: true, key: 'playerState.json' },
betterAutoPlay: { enabled: true },
dynamicNode: { enabled: true },
});
client.once('ready', () => {
console.log(`Logged in as ${client.user.tag}`);
music.init(client.user.id);
});
// !play command
client.on('messageCreate', async (message) => {
if (message.author.bot || !message.guild) return;
if (!message.content.startsWith('!play ')) return;
const query = message.content.slice('!play '.length).trim();
if (!query) return message.reply('Please provide a search query or URL!');
const member = message.member;
const voiceChannel = member.voice?.channel;
if (!voiceChannel) return message.reply('You must be in a voice channel!');
let player = music.get(message.guildId);
if (!player) {
player = music.createConnection({
guildId: message.guildId,
voiceChannel: voiceChannel.id,
textChannel: message.channelId,
deaf: true
});
}
let result;
try {
result = await music.resolve({ query, requester: message.author });
} catch (err) {
return message.reply(`❌ Search failed: ${err.message}`);
}
const { loadType, tracks, playlistInfo } = result;
if (loadType === 'playlist' && playlistInfo) {
player.queue.addPlaylist(tracks, playlistInfo);
message.channel.send(`📀 Playlist: **${playlistInfo.name}** with **${tracks.length}** tracks`);
if (!player.playing && !player.paused) return player.play();
} else if (loadType === 'search' || loadType === 'track') {
const track = tracks.shift();
track.info.requester = message.author;
player.queue.add(track);
message.channel.send(`🎵 Added: **${track.info.title}**`);
if (!player.playing && !player.paused) return player.play();
} else {
return message.channel.send('❌ No results found.');
}
});
// Forward raw voice events to TuneLink for voice connection support
client.on('raw', (packet) => {
if (packet.t === 'VOICE_STATE_UPDATE' || packet.t === 'VOICE_SERVER_UPDATE') {
music.updateVoiceState(packet);
}
});
client.login(process.env.DISCORD_TOKEN);Handling Auto-Resume Events
TuneLink emits a dedicated autoResume event when playback resumes after a node failover or reconnect. This allows you to distinguish between normal track starts and auto-resume:
music.on('autoResume', (player, track, payload) => {
// Handle auto-resume (e.g., notify users, update UI, etc.)
console.log(`Auto-resumed: ${track.info.title} at ${Math.floor((payload.position || 0)/1000)}s`);
});🗂️ Project Structure
| File | Description | |-----------------|--------------------------------------------------| | TuneLink.js | Main orchestration, node/player management, API. | | Player.js | Playback, auto-resume, queue, VC logic. | | Queue.js | Advanced queue management. | | Track.js | Track metadata, search, resolve. | | Node.js | Node management, failover, health. | | Connection.js | WebSocket/voice connection logic. | | Rest.js | Lavalink REST API wrapper. | | Filters.js | Audio filters (EQ, timescale, etc). | | Plugin.js | Plugin system for extensions. |
🌟 Showcase
Are you using TuneLink in your bot? Open a PR to get featured here!
| Bot Name | Invite Link | Support Server | |----------|-------------|---------------| | YourBot | Invite | Server |
📌 Requirements
- Node.js v18 or higher
- Lavalink server (Guide)
- Java v18 or higher (for Lavalink)
- Discord Bot Token (Guide)
🤝 Contributing
Contributions are welcome! Please open issues or pull requests for bugs, features, or improvements.
👥 Contributors
💬 Support & Community
📝 License
This project is licensed under the MIT License.
