lithora
v1.0.0
Published
The most advanced Lavalink v4 wrapper with Discord.js cluster/shard compatibility
Maintainers
Readme
Lithora Advanced - The Ultimate Lavalink v4 Wrapper
Lithora Advanced is the most sophisticated and production-ready Lavalink v4 wrapper built in pure JavaScript, specifically designed to integrate seamlessly with Discord.js's native cluster and shard management systems.
🌟 Why Lithora Advanced?
⚡ Built for Scale
- Discord.js Native Integration - Works perfectly with Discord.js cluster and shard managers
- No Internal Conflicts - External cluster/shard management for maximum flexibility
- Advanced Load Balancing - Intelligent node selection with penalty-based algorithms
- Automatic Failover - Seamless player migration during node failures
🎵 Feature-Rich Audio Experience
- Comprehensive Queue System - Shuffle, repeat modes, history, statistics
- Advanced Audio Filters - 15+ built-in filters with custom filter creation
- Real-time Player Management - Position tracking, volume control, seeking
- Multi-Platform Search - YouTube, Spotify, SoundCloud, and more
🛡️ Production Ready
- Robust Error Handling - Comprehensive error recovery and retry mechanisms
- Performance Monitoring - Real-time statistics and health checks
- Memory Efficient - Optimized for long-running applications
- TypeScript Ready - Full type definitions included
📊 Cluster/Shard Management Comparison
| Feature | Built-in Management | Discord.js Native | Lithora Advanced Approach | |---------|-------------------|------------------|-------------------------| | Shard Routing | Internal implementation | Discord.js handles | External (Discord.js) | | Cluster Management | Built-in cluster manager | discord.js ClusterManager | Compatible with both | | Integration Complexity | Low (self-contained) | Moderate | Low (designed for it) | | Flexibility | Limited to wrapper design | High | Maximum flexibility | | Conflict Resolution | Potential conflicts | No conflicts | Zero conflicts | | Scaling Control | Wrapper-dependent | Full Discord.js control | Best of both worlds | | Memory Usage | Higher (duplicate logic) | Optimized | Optimized | | Update Compatibility | Wrapper-dependent | Always compatible | Always compatible |
✅ Why External Management is Superior
- No Conflicts - Lithora doesn't interfere with Discord.js's proven cluster/shard logic
- Better Performance - No duplicate shard management code
- Easier Scaling - Use Discord.js's battle-tested scaling solutions
- Simpler Debugging - One less layer of complexity
- Future Proof - Always compatible with Discord.js updates
🚀 Installation
npm install lithora ws node-fetch eventemitter3 discord.js🎯 Quick Start
Basic Setup
const { Client, GatewayIntentBits } = require('discord.js');
const { Lithora, DiscordJSConnector } = require('lithora');
const client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent
]
});
const lithora = new Lithora(new DiscordJSConnector(client), {
nodes: [
{
name: 'main',
host: 'localhost',
port: 2333,
password: 'youshallnotpass'
}
],
resume: true,
autoResume: true,
moveOnDisconnect: true
});
lithora.on('ready', (nodes) => {
console.log(`🎵 Lithora ready! Connected to ${nodes.length} node(s)`);
});
client.once('ready', async () => {
console.log(`🤖 Logged in as ${client.user.tag}`);
await lithora.connect();
});
client.login(process.env.DISCORD_TOKEN);Discord.js Cluster Integration
// cluster-manager.js
const { ClusterManager } = require('discord.js');
const path = require('path');
const manager = new ClusterManager(path.join(__dirname, 'bot.js'), {
totalShards: 'auto',
totalClusters: 'auto',
mode: 'process',
token: process.env.DISCORD_TOKEN
});
manager.on('clusterCreate', cluster => {
console.log(`🚀 Cluster ${cluster.id} launched`);
});
manager.spawn();
// bot.js
const { Client } = require('discord.js');
const { Lithora, DiscordJSConnector } = require('lithora');
const client = new Client({ /* your intents */ });
// Multiple nodes for high availability
const lithora = new Lithora(new DiscordJSConnector(client), {
nodes: [
{
name: 'node1',
host: 'lavalink1.example.com',
port: 2333,
password: 'password1'
},
{
name: 'node2',
host: 'lavalink2.example.com',
port: 2333,
password: 'password2'
}
],
moveOnDisconnect: true // Auto-migrate players on node failure
});
client.once('ready', async () => {
await lithora.connect();
console.log(`Cluster ${process.env.CLUSTER} ready with ${client.ws.totalShards} shards`);
});
client.login(process.env.DISCORD_TOKEN);🎮 Player Management
Creating and Using Players
// Create a player
const player = await lithora.createPlayer({
guildId: message.guild.id,
channelId: voiceChannel.id,
autoPlay: true,
autoLeave: true,
autoLeaveDelay: 30000
});
// Search and play
const result = await lithora.search('Never Gonna Give You Up', 'ytsearch');
if (result.loadType === 'search' && result.data.length > 0) {
const track = result.data[0];
player.queue.add(track);
if (!player.playing) {
await player.play();
}
}Advanced Queue Operations
// Queue management
player.queue.add([track1, track2, track3]); // Add multiple tracks
player.queue.shuffle(); // Shuffle queue
player.queue.setRepeatMode('QUEUE'); // Repeat entire queue
player.queue.move(0, 5); // Move track from position 0 to 5
// Queue information
console.log(`Queue: ${player.queue.size} tracks, ${player.queue.duration}ms total`);
console.log(`Current: ${player.queue.current?.info.title}`);
console.log(`Repeat: ${player.queue.repeatMode}, Shuffled: ${player.queue.shuffled}`);
// Queue events
player.queue.on('trackAdd', (tracks) => {
console.log(`Added ${tracks.length} track(s) to queue`);
});
player.queue.on('trackChange', (track) => {
console.log(`Now playing: ${track.info.title}`);
});🎛️ Audio Filters
Built-in Filter Presets
const { Utilities } = require('lithora');
// Apply preset filters
await player.setFilters(Utilities.createBassBoost(0.5));
await player.setFilters(Utilities.createNightcore(1.2, 1.2));
await player.setFilters(Utilities.create8D(0.2));
await player.setFilters(Utilities.createKaraoke());
// Get all presets
const presets = Utilities.getFilterPresets();
await player.setFilters(presets.BASS_BOOST);Custom Filter Creation
// Custom equalizer
const customEQ = Utilities.createEqualizer([
0.25, 0.5, 0.75, 0.5, 0.25, // Low frequencies
0, 0, 0, 0, 0, // Mid frequencies
-0.25, -0.5, -0.25, 0, 0 // High frequencies
]);
// Combine multiple filters
const combined = Utilities.combineFilters(
Utilities.createBassBoost(0.3),
Utilities.createTremolo(2.0, 0.5),
Utilities.createLowPass(15.0)
);
await player.setFilters(combined);Available Filters
- Equalizer - 15-band frequency adjustment
- Bass Boost - Enhanced low frequencies
- Nightcore - Speed and pitch increase
- Vaporwave - Speed and pitch decrease
- 8D Audio - Rotation effect
- Karaoke - Vocal isolation/removal
- Tremolo - Volume oscillation
- Vibrato - Pitch oscillation
- Distortion - Audio distortion effects
- Low Pass - High frequency filtering
📊 Monitoring & Statistics
Node Statistics
// Get node statistics
const nodeStats = lithora.nodes.getStats();
console.log(`Nodes: ${nodeStats.connected}/${nodeStats.total} connected`);
console.log(`Average ping: ${nodeStats.averagePing}ms`);
// Monitor node health
lithora.on('nodeStats', (node, stats) => {
console.log(`Node ${node.options.name}: ${stats.players} players, ${stats.cpu.systemLoad}% CPU`);
});Player Statistics
// Get player statistics
const playerStats = lithora.players.getStats();
console.log(`Players: ${playerStats.total} total, ${playerStats.playing} playing`);
// Queue statistics
const queueStats = player.queue.getStats();
console.log(`Queue: ${queueStats.size} tracks, ${queueStats.duration}ms duration`);🔧 Advanced Configuration
Node Configuration
const lithora = new Lithora(connector, {
nodes: [
{
name: 'primary',
host: 'lavalink.example.com',
port: 2333,
password: 'secure_password',
secure: true, // Use WSS/HTTPS
resume: true,
resumeTimeout: 60,
reconnectTries: 3,
reconnectInterval: 5000,
timeout: 30000
}
],
moveOnDisconnect: true,
autoResume: true,
defaultSearchPlatform: 'ytsearch'
});Player Options
const player = await lithora.createPlayer({
guildId: '123456789',
channelId: '987654321',
deaf: true,
mute: false,
autoPlay: true, // Auto-play next track
autoLeave: true, // Auto-leave when queue is empty
autoLeaveDelay: 30000 // Delay before leaving (ms)
});🔍 Event Handling
Core Events
// Lithora events
lithora.on('ready', (nodes) => {});
lithora.on('nodeConnect', (node) => {});
lithora.on('nodeDisconnect', (node) => {});
lithora.on('playerCreate', (player) => {});
lithora.on('error', (error) => {});
// Player events
player.on('trackStart', (player, track) => {});
player.on('trackEnd', (player, track, reason) => {});
player.on('trackException', (player, track, exception) => {});
player.on('playerUpdate', (player, state) => {});
// Queue events
player.queue.on('trackAdd', (tracks) => {});
player.queue.on('trackChange', (track) => {});
player.queue.on('queueShuffle', (enabled) => {});🌐 Multi-Platform Search
Supported Platforms
const { SearchSources } = require('lithora');
// Search different platforms
await lithora.search('song name', SearchSources.YOUTUBE);
await lithora.search('artist name', SearchSources.YOUTUBE_MUSIC);
await lithora.search('playlist', SearchSources.SOUNDCLOUD);
await lithora.search('album', SearchSources.SPOTIFY);
// Direct URLs (auto-detected)
await lithora.search('https://youtube.com/watch?v=...');
await lithora.search('https://soundcloud.com/...');📈 Performance Optimization
Load Balancing
// Automatic load balancing
const idealNode = lithora.nodes.getLeastUsed();
console.log(`Best node: ${idealNode.options.name} (${idealNode.penalties} penalties)`);
// Manual load rebalancing
const results = await lithora.nodes.rebalanceLoad();
console.log(`Rebalanced ${results.length} players`);Memory Management
// Cleanup disconnected players
const cleaned = lithora.players.cleanup();
console.log(`Cleaned up ${cleaned} disconnected players`);
// Get memory usage
const { Utilities } = require('lithora');
const memory = Utilities.getMemoryUsage();
console.log(`Memory usage: ${memory.heapUsed} / ${memory.heapTotal}`);🚀 Production Deployment
Docker Compose Example
version: '3.8'
services:
lavalink:
image: ghcr.io/lavalink-devs/lavalink:4-alpine
ports:
- "2333:2333"
volumes:
- ./application.yml:/opt/Lavalink/application.yml
restart: unless-stopped
bot:
build: .
environment:
- DISCORD_TOKEN=${DISCORD_TOKEN}
- LAVALINK_HOST=lavalink
- LAVALINK_PASSWORD=${LAVALINK_PASSWORD}
depends_on:
- lavalink
restart: unless-stoppedPM2 Cluster Configuration
// ecosystem.config.js
module.exports = {
apps: [{
name: 'music-bot',
script: 'cluster-manager.js',
instances: 1,
autorestart: true,
watch: false,
max_memory_restart: '1G',
env: {
NODE_ENV: 'production'
}
}]
};🔧 Troubleshooting
Common Issues
Connection Timeouts
// Increase timeout values
const lithora = new Lithora(connector, {
nodes: [{
// ... other options
timeout: 60000,
reconnectInterval: 10000
}]
});Memory Leaks
// Proper cleanup
process.on('SIGINT', async () => {
await lithora.destroy();
process.exit(0);
});Node Failures
// Enable automatic failover
const lithora = new Lithora(connector, {
moveOnDisconnect: true,
autoResume: true
});📚 API Reference
Classes
Lithora- Main wrapper classLithoraNode- Individual Lavalink nodeLithoraPlayer- Audio player instanceLithoraQueue- Advanced queue systemNodeManager- Node management and load balancingPlayerManager- Player lifecycle managementDiscordJSConnector- Discord.js integration
Utilities
LithoraUtil- Utility functions and filter creationLithoraError- Enhanced error handlingConstants- All constants and enums
🤝 Contributing
We welcome contributions! Please read our Contributing Guide for details.
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🔗 Links
⭐ Acknowledgments
- Lavalink - The amazing audio server
- Discord.js - The Discord library we integrate with
- All contributors and users who make this project possible
Made with ❤️ for the Discord bot community
