UNPKG

lavalink-client

Version:

Easy, flexible and feature-rich lavalink@v4 Client. Both for Beginners and Proficients. - Supports NodeLink@v3 too.

786 lines (661 loc) • 30.2 kB
<div style="font-family: Arial, sans-serif; border: 1px solid #fab788; border-radius: 15px; padding: 25px; "> <h1 align="center" style="color: #fab788; border-bottom: 2px solid #3498db; padding-bottom: 10px;">Lavalink Client</h1> <p align="center" style="font-size: 1.2em; color: #555;">An easy, flexible, and feature-rich Lavalink v4 Client for both beginners and experts.</p> <div align="center"> <p> <img src="https://madewithlove.now.sh/at?heart=true&template=for-the-badge" alt="Made with love in Austria"> <img alt="Made with TypeScript" src="https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white"> </p> <p> <a href="https://www.npmjs.com/package/lavalink-client"> <img src="https://img.shields.io/npm/v/lavalink-client.svg?maxAge=3600&style=for-the-badge&logo=npm&logoColor=red" alt="NPM Version" /> </a> <a href="https://www.npmjs.com/package/lavalink-client"> <img src="https://img.shields.io/npm/dt/lavalink-client.svg?maxAge=3600&style=for-the-badge&logo=npm&logoColor=red" alt="NPM Downloads" /> </a> <a href="https://tomato6966.github.io/lavalink-client/"> <img src="https://img.shields.io/badge/Documentation-%230288D1.svg?style=for-the-badge&logo=gitbook&logoColor=white" alt="Get Started Now"> </a> </p> <p> <a href="https://www.npmjs.com/package/lavalink-client"><img src="https://nodei.co/npm/lavalink-client.png?downloads=true&stars=true" alt="NPM Install: lavalink-client" /></a> </p> </div> --- ## šŸš€ Features - šŸ’Æ **Lavalink v4 Native:** Full support for Lavalink v4, including its powerful plugin ecosystem. - āœ… **Detailed Player-Destroy Reasons:** Understand precisely why a player was destroyed (e.g., channel deleted, bot disconnected). - ✨ **Flexible Queue Stores:** Use the default in-memory store or bring your own (Redis, databases, etc.) to sync queues across multiple processes. - šŸŽ¶ **Unresolved Tracks:** Supports unresolved track objects, fetching full data only when a track is about to play, saving API requests and resources. - šŸŽšļø **Built-in Filters & EQ:** Easy-to-use management for audio filters and equalizers. - šŸ” **Advanced Queue Filtering:** Search and filter tracks in the queue by title, author, duration, and more with powerful query options. - āš™ļø **Advanced Player Options:** Fine-tune player behavior for disconnects, empty queues, volume handling, and more. - šŸ›”ļø **Lavalink-Side Validation:** Ensures you only use filters, plugins, and sources that your Lavalink node actually supports. - šŸ”’ **Client-Side Validation:** Whitelist and blacklist URLs or domains to prevent unwanted requests and protect your bot. - šŸ§‘ā€šŸ’» **Developer-Friendly:** A memory-efficient design with a clean, intuitive API that mirrors Lavalink's own implementation. - šŸ¤– **Automated Handling:** Automatically handles track skipping on errors, voice channel deletions, server-wide mutes, and much more. --- ## šŸ“¦ Installation **Latest Stable Version: `v2.9.x`** <details> <summary><strong>šŸ‘‰ via NPM</strong></summary> ```bash # Stable (install release) npm install --save lavalink-client # Development (Install github dev-branch) npm install --save tomato6966/lavalink-client ``` </details> <details> <summary><strong>šŸ‘‰ via YARN</strong></summary> ```bash # Stable (install release) yarn add lavalink-client # Development (Install github dev-branch) yarn add tomato6966/lavalink-client ``` </details> <details> <summary><strong>šŸ‘‰ via BUN</strong></summary> ```bash # Stable (install release) bun add lavalink-client # Development (Install github dev-branch) bun add tomato6966/lavalink-client ``` </details> <details> <summary><strong>šŸ‘‰ via pnpm</strong></summary> ```bash # Stable (install release) pnpm add lavalink-client # Development (Install github dev-branch) pnpm add tomato6966/lavalink-client ``` </details> ## šŸ“– Documentation & Guides - **[Full Documentation](https://tomato6966.github.io/lavalink-client/)** - Your starting point for everything. - **[Manager Events](https://tomato6966.github.io/lavalink-client/extra/manager-events)** - Handle track, player, and general client events. - **[NodeManager Events](https://tomato6966.github.io/lavalink-client/extra/node-events)** - Manage node connections, errors, and logs. - **[Session Resuming Guide](https://tomato6966.github.io/lavalink-client/extra/resuming)** - Learn how to implement session resuming for seamless restarts. --- # Node Link This client can be used with nodelink too, but because nodelink's websocket is different than the one from lavalink, you need to disable a few things on the NODE OPTIONS / NODE PROPERTIES: Import the NodeType Enum: ``` import { NodeType } from "lavalink-client" ``` // You must assign NodeLink enum to nodeType of the node options, before creating the node. ```ts nodeOptions.nodeType = NodeType.NodeLink; ``` this can be done directly when creating the node in the lavalinkmanager. ```ts client.lavalink = new LavalinkManager({ nodes: [ { host: "localhost", nodeType: NodeType.NodeLink, // provide nodeType "nodelink" to it. }, ], }); // or here if you need a bigger example. client.lavalink = new LavalinkManager({ nodes: [ { authorization: "youshallnotpass", // The password for your Lavalink server host: "localhost", port: 2333, id: "Main Node", // set to nodeLink nodeType: NodeType.NodeLink, // provide it here }, // you can also use the util like this, and it will return a valid node option object. must start with: lavalink:// | nodelink:// // parseLavalinkConnUrl("nodelink://<nodeId>:<nodeAuthorization(Password)>@<NodeHost>:<NodePort>") ], // A function to send voice server updates to the Lavalink client sendToShard: (guildId, payload) => { const guild = client.guilds.cache.get(guildId); if (guild) guild.shard.send(payload); }, autoSkip: true, client: { id: process.env.CLIENT_ID, // Your bot's user ID username: "MyBot", }, }); ``` Now if you want to use NodeLink specific functions, you can use the type assertion checker function: ```ts if (node.isNodeLink()) { // node is now typed as NodeLink node.addMixerLayer(); } else if (node.isLavalinkNode()) { // node is now typed as LavalinkNode } else { // node is now typed as whatever it is.. } ``` or you have to assert the type... ``` const node = client.lavalink.lavalinkManager.getNode("id") as NodeLinkNode; node.addMixerLayer() ``` āš ļø NODELINK does not really require you to run "instaUpdateFiltersFix" on player creation. ### NodeLink Specific Methods - **`node.getYoutubeOAUTH(refreshToken)`**: Exchange a Refresh Token for an Access Token. [Docs](https://nodelink.js.org/docs/api/nodelink-features#oauth) - **`node.updateYoutubeOAUTH(refreshToken)`**: Update the OAUTH token. [Docs](https://nodelink.js.org/docs/api/nodelink-features#oauth) - **`node.getChapters(player, track?)`**: Retrieve Chapters of Youtube Videos. [Docs](https://nodelink.js.org/docs/api/nodelink-features#loadchapters) - **`node.nodeLinkLyrics(player, track?, language?)`**: Retrieve Lyrics of Youtube Videos. [Docs](https://nodelink.js.org/docs/api/nodelink-features#lyrics--chapters) - **`node.getDirectStream(track)`**: Stream audio directly from NodeLink. [Docs](https://nodelink.js.org/docs/api/nodelink-features#direct-streaming) - **`node.listMixerLayers(player)`**: Retrieves a list of currently active mix layers. [Docs](https://nodelink.js.org/docs/api/rest#get-active-mixes) - **`node.addMixerLayer(player, track, volume)`**: Adds a new audio track to be mixed. [Docs](https://nodelink.js.org/docs/api/rest#add-mix-layer) - **`node.removeMixerLayer(player, mixId)`**: Removes a specific mix layer. [Docs](https://nodelink.js.org/docs/api/rest#remove-mix-layer) - **`node.updateMixerLayerVolume(player, mixId, volume)`**: Updates the volume of a specific mix layer. [Docs](https://nodelink.js.org/docs/api/rest#update-mix-volume) - **`node.changeAudioTrackLanguage(player, language_audioTrackId)`**: Changes the current language of the audio. [Docs](https://nodelink.js.org/docs/api/nodelink-features#additional-filters) - **`node.updateYoutubeConfig(refreshToken?, visitorData?)`**: Updates the YouTube configuration. [Docs](https://nodelink.js.org/docs/api/nodelink-features#update-config) - **`node.getYoutubeConfig(validate?)`**: Gets the YouTube configuration. - **`node.getConnectionMetrics()`**: Get connection metrics. [Docs](https://nodelink.js.org/docs/api/rest#node-information) - **`node.loadDirectStream(track, volume, position, filters)`**: Stream raw PCM audio. [Docs](https://nodelink.js.org/docs/api/nodelink-features#loadstream) ### NodeLink Specific Sources (`SourcesRecord`) By default, the client uses a generic `DefaultSources` record to map human-friendly prefixes like `youtube:`, `spotify:`, etc. to Lavalink search sources. For NodeLink, you can override this behavior to use the NodeLink-specific source prefixes (including `admsearch`, `audiomack`, `gaanasearch`, `gtts`, `pipertts`, etc.) via the `NodeLinkDefaultSources` export and the `ManagerUtils#SourcesRecord` property: ```ts import { LavalinkManager, NodeType, NodeLinkNode, NodeLinkDefaultSources } from "lavalink-client"; // create the manager as usual, but with a NodeLink node client.lavalink = new LavalinkManager({ nodes: [ { authorization: "youshallnotpass", host: "localhost", port: 2333, id: "nodelink-main", nodeType: NodeType.NodeLink, }, ], sendToShard: (guildId, payload) => client.guilds.cache.get(guildId)?.shard?.send(payload), autoSkip: true, client: { id: envConfig.clientId, username: "MyBot", }, }); // IMPORTANT: Override the default source mapping so `ytsearch:`, `amsearch:`, `admsearch:`, etc. // use the NodeLink-specific prefixes. client.lavalink.utils.SourcesRecord = NodeLinkDefaultSources; // Optional: You can also provide your own custom record extending NodeLinkDefaultSources: client.lavalink.utils.SourcesRecord = { ...NodeLinkDefaultSources, // custom alias that only your bot understands, which resolves to a NodeLink source mycustomsource: "ytsearch", }; ``` This makes all helper methods that use `ManagerUtils` (such as `transformQuery`, `transformLavaSearchQuery` and `validateSourceString`) work with NodeLink’s extended search prefixes when you are connected to a NodeLink node. ### NodeLink Specififc Events? ```ts // NodeLink specific events client.lavalink.nodeManager.on("nodeLinkEvent", (node, eventName, player, track, payload) => { switch (eventName) { // -------------Player LifeCycle Events------------- // https://nodelink.js.org/docs/api/websocket#playercreatedevent case "PlayerCreatedEvent": { // { "guildId": "987654321098765432", "track": null, "paused": false, "volume": 100 } const playerInfo = payload.player; console.log(`Player created in guildId: ${playerInfo.guildId}`); } break; // https://nodelink.js.org/docs/api/websocket#playerdestroyedevent case "PlayerDestroyedEvent": { // "987654321098765432" const playerInfo = payload.guildId; console.log(`Player destroyed in guildId: ${playerInfo.guildId}`); } break; // https://nodelink.js.org/docs/api/websocket#playerconnectedevent case "PlayerConnectedEvent": { // { "sessionId": "abc", "token": "token", "endpoint": "us-central123.discord.media", "channelId": "123456789012345678" } const playerInfo = payload.voice; console.log(`Player connected in guildId: ${playerInfo.guildId}`); } break; // https://nodelink.js.org/docs/api/websocket#playerreconnectingevent case "PlayerReconnectingEvent": { // { "sessionId": "abc", "token": "token", "endpoint": "us-central123.discord.media", "channelId": "123456789012345678" } const playerInfo = payload.voice; console.log(`Player reconnecting in guildId: ${playerInfo.guildId}`); } break; // -------------Player State Events------------- // https://nodelink.js.org/docs/api/websocket#volumechangedevent case "VolumeChangedEvent": { // "guildId": "987654321098765432", // "volume": 100 const { guildId, volume } = payload; console.log(`Player volume changed in guildId: ${guildId} to ${volume}`); } break; // https://nodelink.js.org/docs/api/websocket#filterschangedevent case "FiltersChangedEvent": { // { ...Filtersdata... } const { guildId, filters } = payload; console.log(`Player filters changed in guildId: ${guildId}, new Data: `, filters); } break; // https://nodelink.js.org/docs/api/websocket#seekevent case "SeekEvent": { // "guildId": "987654321098765432", // "position": 10000, const { guildId, position } = payload; console.log(`Player seeked in guildId: ${guildId}, new position: ${position}`); } break; // https://nodelink.js.org/docs/api/websocket#pauseevent case "PauseEvent": { // "guildId": "987654321098765432", // "paused": true const { guildId, paused } = payload; console.log(`Player paused in guildId: ${guildId}, paused true/false: ${paused}`); } break; // https://nodelink.js.org/docs/api/websocket#connectionstatusevent case "ConnectionStatusEvent": { // "guildId": "987654321098765432", // "status": "CONNECTED" // "metrics": { ... } const { guildId, status, metrics } = payload; console.log( `Player connection status changed in guildId: ${guildId}, status: ${status}, metrics: `, metrics, ); } break; // -------------LYRICS EVENTS------------- // https://nodelink.js.org/docs/api/websocket#lyrics-events case "LyricsFoundEvent": { // "guildId": "987654321098765432", // "lyrics": "..." const { guildId, lyrics } = payload; console.log(`Lyrics found in guildId: ${guildId}, lyrics: `, lyrics); } break; // https://nodelink.js.org/docs/api/websocket#lyricslineevent case "LyricsLineEvent": { // "guildId": "987654321098765432", // "lineIndex": 0, // "line": "..." const { guildId, lineIndex, line } = payload; console.log(`Lyrics line in guildId: ${guildId}, lineIndex: ${lineIndex}, line: `, line); } break; // https://nodelink.js.org/docs/api/websocket#lyricsnotfoundevent case "LyricsNotFoundEvent": { // "guildId": "987654321098765432", const { guildId } = payload; console.log(`Lyrics not found in guildId: ${guildId}`); } break; // -------------AUDIO MIXER EVENTS------------- // https://nodelink.js.org/docs/api/websocket#mixstartedevent case "MixStartedEvent": { // "guildId": "987654321098765432", // "mixId": "123456789012345678" // "volume": 0.8, // "track": { ...TrackData... } const { guildId, mixId, volume, track } = payload.player; console.log( `Player mix started in guildId: ${guildId}, mixId: ${mixId}, volume: ${volume}, track: `, track, ); } break; // https://nodelink.js.org/docs/api/websocket#mixendedevent case "MixEndedEvent": { // "guildId": "987654321098765432", // "mixId": "123456789012345678", // "reason": "USER_STOPPED" const { guildId, mixId, reason } = payload; console.log(`Player mix ended in guildId: ${guildId}, mixId: ${mixId}, reason: ${reason}`); } break; } }); ``` --- ## šŸ’– Used In This client powers various Discord bots: - **[Mivator](https://discord.gg/5dUb7M2qCj)** (Public Bot by @Tomato6966) - **[Betty](https://betty.bot/?utm_source=lavalink-client)** (Public Bot by @fb_sean) - **[Nero](https://betty.bot/?utm_source=lavalink-client)** (Public Bot by @fb_sean) - **Bots by Contributors:** - [Mintone](https://mintone.tech/) (@appujet) - [Stelle](https://github.com/Ganyu-Studios/stelle-music) (@EvilG-MC) - [Panais](https://panais.xyz/) (@LucasB25) - [Akyn](https://akynbot.vercel.app/) (@notdeltaxd) - [ARINO](https://site.arinoapp.qzz.io/) (@ryanwtf88) - [iHorizon](https://github.com/ihrz/ihrz) (@iHorizon) - **Bots By Community (Users):** - [Soundy](https://github.com/idMJA/Soundy) (@idMJA) - [BeatBot](https://getbeatbot.vercel.app/) (@zenitsujs) - [Atom Music](https://top.gg/bot/1320469557411971165) (@sakshamyep) - [All Time Bot](https://top.gg/bot/1163027457671180418) (@PeterGamez) - [BeatDock](https://github.com/lazaroagomez/BeatDock) (@lazaroagomez) - [Nazha](https://top.gg/bot/1124681788070055967) (@Nazha-Team) - [Arii Music](https://arimusic.me/) (@friston_ae) --- ## šŸ› ļø Configuration Examples ### Basic Setup A minimal example to get you started quickly. ```typescript import { LavalinkManager } from "lavalink-client"; import { Client, GatewayIntentBits } from "discord.js"; // example for a discord bot // Extend the Client type to include the lavalink manager declare module "discord.js" { interface Client { lavalink: LavalinkManager; } } const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates], }); client.lavalink = new LavalinkManager({ nodes: [ { authorization: "youshallnotpass", // The password for your Lavalink server host: "localhost", port: 2333, id: "Main Node", }, // you can also use the util like this, and it will return a valid node option object. // parseLavalinkConnUrl("nodelink://<nodeId>:<nodeAuthorization(Password)>@<NodeHost>:<NodePort>") ], // A function to send voice server updates to the Lavalink client sendToShard: (guildId, payload) => { const guild = client.guilds.cache.get(guildId); if (guild) guild.shard.send(payload); }, autoSkip: true, client: { id: process.env.CLIENT_ID, // Your bot's user ID username: "MyBot", }, }); // Listen for the 'raw' event from discord.js and forward it client.on("raw", (d) => client.lavalink.sendRawData(d)); client.on("ready", () => { console.log(`Logged in as ${client.user.tag}!`); // Initialize the Lavalink client client.lavalink.init({ ...client.user }); }); client.login(process.env.DISCORD_TOKEN); ``` <details> <summary><strong>šŸ”© Complete Configuration Example (almost all Options)</strong></summary> ```typescript import { LavalinkManager, QueueChangesWatcher, QueueStoreManager, StoredQueue } from "lavalink-client"; import { RedisClientType, createClient } from "redis"; import { Client, GatewayIntentBits, User } from "discord.js"; // It's recommended to extend the Client type declare module "discord.js" { interface Client { lavalink: LavalinkManager; redis: RedisClientType; } } const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates], }); client.lavalink = new LavalinkManager({ nodes: [ { authorization: "youshallnotpass", host: "localhost", port: 2333, id: "testnode", secure: false, // Set to true for wss:// retryAmount: 5, retryDelay: 10_000, // 10 seconds }, // you can also use the util like this, and it will return a valid node option object. must start with: lavalink:// | nodelink:// // parseLavalinkConnUrl("lavalink://<nodeId>:<nodeAuthorization(Password)>@<NodeHost>:<NodePort>") ], sendToShard: (guildId, payload) => client.guilds.cache.get(guildId)?.shard?.send(payload), autoSkip: true, // automatically play the next song of the queue, on: trackend, trackerror, trackexception client: { id: process.env.CLIENT_ID, username: "TESTBOT", }, playerOptions: { applyVolumeAsFilter: false, clientBasedPositionUpdateInterval: 50, defaultSearchPlatform: "ytmsearch", volumeDecrementer: 0.75, onDisconnect: { autoReconnect: true, destroyPlayer: false, }, onEmptyQueue: { destroyAfterMs: 30_000, // function get's called onqueueempty, and if there are songs added to the queue, it continues playing. if not then not (autoplay functionality) // autoPlayFunction: async (player) => { /* ... */ }, }, useUnresolvedData: true, }, queueOptions: { maxPreviousTracks: 10, queueStore: new MyCustomRedisStore(client.redis), queueChangesWatcher: new MyCustomQueueWatcher(client), }, // Whitelist/Blacklist links or words linksAllowed: true, linksBlacklist: ["somebadsite.com"], linksWhitelist: [], advancedOptions: { debugOptions: { noAudio: false, playerDestroy: { dontThrowError: false, debugLog: false }, }, }, }); client.on("raw", (d) => client.lavalink.sendRawData(d)); client.on("ready", () => client.lavalink.init({ ...client.user })); // Example Custom Redis Queue Store class MyCustomRedisStore implements QueueStoreManager { private redis: RedisClientType; constructor(redisClient: RedisClientType) { this.redis = redisClient; } private key(guildId: string) { return `lavalinkqueue_${guildId}`; } async get(guildId: string) { return await this.redis.get(this.key(guildId)); } async set(guildId: string, data: string) { return await this.redis.set(this.key(guildId), data); } async delete(guildId: string) { return await this.redis.del(this.key(guildId)); } async parse(data: string): Promise<Partial<StoredQueue>> { return JSON.parse(data); } stringify(data: Partial<StoredQueue>): string { return JSON.stringify(data); } } // Example Custom Queue Watcher class MyCustomQueueWatcher implements QueueChangesWatcher { private client: Client; constructor(client: Client) { this.client = client; } shuffled(guildId: string) { console.log(`Queue shuffled in guild: ${guildId}`); } tracksAdd(guildId: string, tracks: any[], position: number) { console.log(`${tracks.length} tracks added at position ${position} in guild: ${guildId}`); } tracksRemoved(guildId: string, tracks: any[], position: number) { console.log(`${tracks.length} tracks removed at position ${position} in guild: ${guildId}`); } } ``` </details> --- ## šŸ“¢ Events Listen to events to create interactive and responsive logic. ### Lavalink Manager Events These events are emitted from the main `LavalinkManager` instance and relate to players and tracks. - `playerCreate (player)` - `playerDestroy (player, reason)` - `playerDisconnect (player, voiceChannelId)` - `playerMove (player, oldChannelId, newChannelId)` - `trackStart (player, track)` - `trackEnd (player, track)` - `trackStuck (player, track, payload)` - `trackError (player, track, payload)` - `queueEnd (player)` <details> <summary><strong>šŸ“¢ Example for Manager-Event-Listeners</strong></summary> ```javascript // Example: Listening to a track start event client.lavalink.on("trackStart", (player, track) => { const channel = client.channels.cache.get(player.textChannelId); if (channel) channel.send(`Now playing: ${track.info.title}`); }); // Example: Handling queue end client.lavalink.on("queueEnd", (player) => { const channel = client.channels.cache.get(player.textChannelId); if (channel) channel.send("The queue has finished. Add more songs!"); player.destroy(); }); ``` </details> ### Node Manager Events These events are emitted from `lavalink.nodeManager` and relate to the Lavalink node connections. - `create (node)` - `connect (node)` - `disconnect (node, reason)` - `reconnecting (node)` - `destroy (node)` - `error (node, error, payload)` - `resumed (node, payload, players)` <details> <summary><strong>šŸ“¢ Example for Node-Event-Listeners</strong></summary> ```javascript // Example: Logging node connections and errors client.lavalink.nodeManager.on("connect", (node) => { console.log(`Node "${node.id}" connected!`); }); client.lavalink.nodeManager.on("error", (node, error) => { console.error(`Node "${node.id}" encountered an error:`, error.message); }); ``` </details> --- ## šŸ“š Advanced How-To Guides ### How to Implement Session Resuming Resuming allows your music bot to continue playback even after a restart. 1. **Enable Resuming on the Node:** When a node connects, enable resuming with a timeout. 2. **Listen for the `resumed` Event:** This event fires on a successful reconnect, providing all player data from Lavalink. 3. **Re-create Players:** Use the data from the `resumed` event and your own saved data (from a database/store) to rebuild the players and their queues. > šŸ’” **For a complete, working example, see the [official test bot's implementation](https://github.com/Tomato6966/lavalink-client/blob/main/testBot/Utils/handleResuming.ts).** <details> <summary><strong>šŸ’” Principle of how to enable **resuming**</strong></summary> ```javascript // 1. Enable resuming on connect client.lavalink.nodeManager.on("connect", (node) => { // Enable resuming for 5 minutes (300,000 ms) node.updateSession(true, 300_000); }); // 2. Listen for the resumed event client.lavalink.nodeManager.on("resumed", async (node, payload, fetchedPlayers) => { console.log(`Node "${node.id}" successfully resumed with ${fetchedPlayers.length} players.`); for (const lavalinkData of fetchedPlayers) { // 3. Get your saved data (e.g., from Redis/DB) const savedData = await getFromDatabase(lavalinkData.guildId); if (!savedData || !lavalinkData.state.connected) { if (savedData) await deleteFromDatabase(lavalinkData.guildId); continue; // Skip if no saved data or Lavalink reports disconnected } // Re-create the player instance const player = client.lavalink.createPlayer({ guildId: lavalinkData.guildId, voiceChannelId: savedData.voiceChannelId, textChannelId: savedData.textChannelId, // Important: Use the same node that was resumed node: node.id, // Set volume from Lavalink's data, accounting for the volume decrementer volume: lavalinkData.volume, selfDeaf: savedData.selfDeaf, }); // Re-establish voice connection await player.connect(); // Restore player state player.paused = lavalinkData.paused; player.lastPosition = lavalinkData.state.position; player.filterManager.data = lavalinkData.filters; // Restore the queue await player.queue.utils.sync(true, false); // Syncs with your QueueStore // Restore the current track if (lavalinkData.track) { player.queue.current = client.lavalink.utils.buildTrack(lavalinkData.track, savedData.requester); } } }); // Persist player data on updates to use for resuming later client.lavalink.on("playerUpdate", (oldPlayer, newPlayer) => { saveToDatabase(newPlayer.toJSON()); }); // Clean up data when a player is permanently destroyed client.lavalink.on("playerDestroy", (player) => { deleteFromDatabase(player.guildId); }); ``` </details> ### How to Use Plugins Lavalink client supports most of the major lavalink-plugins. The client itself is - for beginner friendly reasons - atm not extendable (via plugins) You can just use the built in functions (sponsor block, lyrics) or search plattforms (deezer, spotify, apple music, youtube, ...) and use the lavalink-plugins without any configuration on the client side. Some plugins require extra-parameters, such as flowerytts: Pass extra parameters to the search function to use plugin-specific features. <details> <summary><strong>How to use the flowerytts plugin</strong></summary> ```javascript // Example for flowertts plugin const query = interaction.options.getString("text"); const voice = interaction.options.getString("voice"); // e.g., "MALE_1" const extraParams = new URLSearchParams(); if (voice) extraParams.append(`voice`, voice); // All params for flowertts can be found here: https://flowery.pw/docs const response = await player.search( { query: `${query}`, // This is used by plugins like ftts to adjust the request extraQueryUrlParams: extraParams, source: "ftts", // Specify the plugin source }, interaction.user, // The requester ); // Add the TTS track to the queue if (response.tracks.length > 0) { player.queue.add(response.tracks[0]); if (!player.playing) player.play(); } ``` </details> </div>