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!