botClient.on("ready",async () => { // only init it when the Bot is ready...awaitbotClient.lavalink.init({ id:botClient.user.id, username:botclient.user.username });});
// The Bot Client, here a discord.js oneconstclient=newClient({ intents: [ GatewayIntentBits.Guilds,GatewayIntentBits.GuildVoiceStates ]}) asClient& { redis:RedisClientType, lavalink:LavalinkManager };
client.lavalink =newLavalinkManager({ 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!
client.on("ready",async () => {awaitclient.lavalink.init({ id:client.user.id, username:client.user.username });});client.on("raw", data =>client.lavalink.sendRawData(data));
Advanced Example Creation
Example with Redis-Queue, Request-Transformer, Autoplay Function, ...
// Create the Lavalink Serverclient.lavalink =newLavalinkManager({ 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:newmyCustomStore(client.redis), queueChangesWatcher:newmyCustomWatcher(client) },});
Custom Queue Store Class (Saving the queue on a redis server)
exportclassmyCustomStoreimplementsQueueStoreManager {private redis:RedisClientType;constructor(redisClient:RedisClientType) {this.redis = redisClient; }asyncget(guildId):Promise<any> {returnawaitthis.redis.get(this.id(guildId)); }asyncset(guildId, stringifiedQueueData):Promise<any> {// await this.delete(guildId); // redis requires you to delete it first;returnawaitthis.redis.set(this.id(guildId), stringifiedQueueData); }asyncdelete(guildId):Promise<any> {returnawaitthis.redis.del(this.id(guildId)); }asyncparse(stringifiedQueueData):Promise<Partial<StoredQueue>> {returnJSON.parse(stringifiedQueueData); }asyncstringify(parsedQueueData):Promise<any> {returnJSON.stringify(parsedQueueData); }// you can add more utils if you need to...privateid(guildId) {return`lavalinkqueue_${guildId}`; // transform the id }}
Custom Queue Changes Watcher Class, for seeing changes within the Queue
exportclassmyCustomWatcherimplementsQueueChangesWatcher {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
exportconstautoPlayFunction=async (player, lastPlayedTrack) => {if(lastPlayedTrack.info.sourceName ==="spotify") {constfiltered=player.queue.previous.filter(v =>v.info.sourceName ==="spotify").slice(0,5);constids=filtered.map(v =>v.info.identifier ||v.info.uri.split("/")?.reverse()?.[0] ||v.info.uri.split("/")?.reverse()?.[1]);if(ids.length>=2) {constres=awaitplayer.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) awaitplayer.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; })); elseconsole.log("Spotify - NOTHING GOT ADDED"); }return; }if(lastPlayedTrack.info.sourceName ==="youtube"||lastPlayedTrack.info.sourceName ==="youtubemusic") {constres=awaitplayer.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) awaitplayer.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; })); elseconsole.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.
exportconstrequesterTransformer= (requester:any):CustomRequester=> {// if it's already the transformed requesterif(typeof requester ==="object"&&"avatar"in requester &&Object.keys(requester).length===3) return requester asCustomRequester; // if it's still a discord.js Userif(typeof requester ==="object"&&"displayAvatarURL"in requester) { // it's a userreturn { id:requester.id, username:requester.username, avatar:requester.displayAvatarURL(), } }// if it's non of the abovereturn { id: requester!.toString(), username:"unknown" }; // reteurn something that makes sense for you!}exportinterfaceCustomRequester { id:string, username:string, avatar?:string,}
Finally, initatlize the Manager and send Raw Data!
client.on("ready",async () => {awaitclient.lavalink.init({ id:client.user.id, username:client.user.username });});client.on("raw", data =>client.lavalink.sendRawData(data));