If the Manager is useable (If at least 1 Node is connected)
.options
The options from the Manager
.players
All the Players of the Manager
.nodeManager
The Node Manager of the Manager
.utils
The Manager's Utils
Methods
Initializes the Manager and connects all Nodes
botClient.on("ready", async () => { // only init it when the Bot is ready...
await botClient.lavalink.init({
id: botClient.user.id,
username: botclient.user.username
});
});
import { Client, GatewayIntentBits } from "discord.js";
import { createClient, RedisClientType } from 'redis';
import { LavalinkManager } from "../src";
// The Bot Client, here a discord.js one
const client = new Client({
intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates ]
}) as Client & { redis: RedisClientType, lavalink: LavalinkManager };
client.lavalink = new LavalinkManager({
nodes: [
{
authorization: "yourverystrongpassword",
host: "localhost",
port: 2333,
id: "testnode",
requestTimeout: 10000,
}
],
sendToShard: (guildId, payload) => client.guilds.cache.get(guildId)?.shard?.send(payload),
autoSkip: true,
client: {
id: envConfig.clientId, // REQUIRED! (at least after the .init)
username: "TESTBOT"
},
playerOptions: {
applyVolumeAsFilter: false,
clientBasedPositionUpdateInterval: 50,
defaultSearchPlatform: "ytmsearch",
volumeDecrementer: 0.75, // on client 100% == on lavalink 75%
onDisconnect: {
autoReconnect: true, // automatically attempts a reconnect, if the bot disconnects from the voice channel, if it fails, it gets destroyed
destroyPlayer: false // overrides autoReconnect and directly destroys the player if the bot disconnects from the vc
},
onEmptyQueue: {
destroyAfterMs: 30_000, // 0 === instantly destroy | don't provide the option, to don't destroy the player
},
useUnresolvedData: true
},
queueOptions: {
maxPreviousTracks: 10
},
});
Finally, initatlize the Manager and send Raw Data!
Example with Redis-Queue, Request-Transformer, Autoplay Function, ...
import { Client, GatewayIntentBits } from "discord.js";
import { createClient, RedisClientType } from 'redis';
import { LavalinkManager, QueueStoreManager , QueueChangesWatcher } from "lavalink-client";
// The Bot Client, here a discord.js one
const client = new Client({
intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates ]
}) as Client & { redis: RedisClientType, lavalink: LavalinkManager };
// Create the Lavalink Server
client.lavalink = new LavalinkManager({
nodes: [
{
authorization: "yourverystrongpassword",
host: "localhost",
port: 2333,
id: "testnode",
requestTimeout: 10000,
}
],
sendToShard: (guildId, payload) => client.guilds.cache.get(guildId)?.shard?.send(payload),
autoSkip: true,
client: {
id: envConfig.clientId, // REQUIRED! (at least after the .init)
username: "TESTBOT"
},
playerOptions: {
applyVolumeAsFilter: false,
clientBasedPositionUpdateInterval: 50,
defaultSearchPlatform: "ytmsearch",
volumeDecrementer: 0.75, // on client 100% == on lavalink 75%
requesterTransformer: requesterTransformer,
onDisconnect: {
autoReconnect: true, // automatically attempts a reconnect, if the bot disconnects from the voice channel, if it fails, it gets destroyed
destroyPlayer: false // overrides autoReconnect and directly destroys the player if the bot disconnects from the vc
},
onEmptyQueue: {
destroyAfterMs: 30_000, // 0 === instantly destroy | don't provide the option, to don't destroy the player
autoPlayFunction: autoPlayFunction,
},
useUnresolvedData: true
},
queueOptions: {
maxPreviousTracks: 10,
queueStore: new myCustomStore(client.redis),
queueChangesWatcher: new myCustomWatcher(client)
},
});
Custom Queue Store Class (Saving the queue on a redis server)
export class myCustomStore implements QueueStoreManager {
private redis:RedisClientType;
constructor(redisClient:RedisClientType) {
this.redis = redisClient;
}
async get(guildId): Promise<any> {
return await this.redis.get(this.id(guildId));
}
async set(guildId, stringifiedQueueData): Promise<any> {
// await this.delete(guildId); // redis requires you to delete it first;
return await this.redis.set(this.id(guildId), stringifiedQueueData);
}
async delete(guildId): Promise<any> {
return await this.redis.del(this.id(guildId));
}
async parse(stringifiedQueueData): Promise<Partial<StoredQueue>> {
return JSON.parse(stringifiedQueueData);
}
async stringify(parsedQueueData): Promise<any> {
return JSON.stringify(parsedQueueData);
}
// you can add more utils if you need to...
private id(guildId) {
return `lavalinkqueue_${guildId}`; // transform the id
}
}
Custom Queue Changes Watcher Class, for seeing changes within the Queue
export class myCustomWatcher implements QueueChangesWatcher {
private client:BotClient;
constructor(client:BotClient) {
this.client = client;
}
shuffled(guildId, oldStoredQueue, newStoredQueue) {
console.log(`${this.client.guilds.cache.get(guildId)?.name || guildId}: Queue got shuffled`)
}
tracksAdd(guildId, tracks, position, oldStoredQueue, newStoredQueue) {
console.log(`${this.client.guilds.cache.get(guildId)?.name || guildId}: ${tracks.length} Tracks got added into the Queue at position #${position}`);
}
tracksRemoved(guildId, tracks, position, oldStoredQueue, newStoredQueue) {
console.log(`${this.client.guilds.cache.get(guildId)?.name || guildId}: ${tracks.length} Tracks got removed from the Queue at position #${position}`);
}
}
Custom Autoplay function, which gets executed, before the "queueEnd" Event fires, and if there is a new track added in the queue, then it plays it. else "queueEnd" will fire
export const autoPlayFunction = async (player, lastPlayedTrack) => {
if(lastPlayedTrack.info.sourceName === "spotify") {
const filtered = player.queue.previous.filter(v => v.info.sourceName === "spotify").slice(0, 5);
const ids = filtered.map(v => v.info.identifier || v.info.uri.split("/")?.reverse()?.[0] || v.info.uri.split("/")?.reverse()?.[1]);
if(ids.length >= 2) {
const res = await player.search({
query: `seed_tracks=${ids.join(",")}`, //`seed_artists=${artistIds.join(",")}&seed_genres=${genre.join(",")}&seed_tracks=${trackIds.join(",")}`;
source: "sprec"
}, lastPlayedTrack.requester).then(response => {
response.tracks = response.tracks.filter(v => v.info.identifier !== lastPlayedTrack.info.identifier); // remove the lastPlayed track if it's in there..
return response;
}).catch(console.warn);
if(res && res.tracks.length) await player.queue.add(res.tracks.slice(0, 5).map(track => {
// transform the track plugininfo so you can figure out if the track is from autoplay or not.
track.pluginInfo.clientData = { ...(track.pluginInfo.clientData||{}), fromAutoplay: true };
return track;
})); else console.log("Spotify - NOTHING GOT ADDED");
}
return;
}
if(lastPlayedTrack.info.sourceName === "youtube" || lastPlayedTrack.info.sourceName === "youtubemusic") {
const res = await player.search({
query:`https://www.youtube.com/watch?v=${lastPlayedTrack.info.identifier}&list=RD${lastPlayedTrack.info.identifier}`,
source: "youtube"
}, lastPlayedTrack.requester).then(response => {
response.tracks = response.tracks.filter(v => v.info.identifier !== lastPlayedTrack.info.identifier); // remove the lastPlayed track if it's in there..
return response;
}).catch(console.warn);
if(res && res.tracks.length) await player.queue.add(res.tracks.slice(0, 5).map(track => {
// transform the track plugininfo so you can figure out if the track is from autoplay or not.
track.pluginInfo.clientData = { ...(track.pluginInfo.clientData||{}), fromAutoplay: true };
return track;
})); else console.log("YT - NOTHING GOT ADDED");
return;
}
return
}
Custom requestTransformer Function, which allows you to provide just a User Object into the "requester" parameter(s) and then transform it to save memory!
Attention: This function might get executed on already transformed requesters, if you re-use it.
export const requesterTransformer = (requester:any):CustomRequester => {
// if it's already the transformed requester
if(typeof requester === "object" && "avatar" in requester && Object.keys(requester).length === 3) return requester as CustomRequester;
// if it's still a discord.js User
if(typeof requester === "object" && "displayAvatarURL" in requester) { // it's a user
return {
id: requester.id,
username: requester.username,
avatar: requester.displayAvatarURL(),
}
}
// if it's non of the above
return { id: requester!.toString(), username: "unknown" }; // reteurn something that makes sense for you!
}
export interface CustomRequester {
id: string,
username: string,
avatar?: string,
}
Finally, initatlize the Manager and send Raw Data!