lavalink-client 2.7.8 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (6) hide show
  1. package/README.md +726 -466
  2. package/dist/index.d.mts +2360 -1899
  3. package/dist/index.d.ts +2360 -1899
  4. package/dist/index.js +1653 -539
  5. package/dist/index.mjs +1650 -539
  6. package/package.json +79 -81
package/README.md CHANGED
@@ -1,466 +1,726 @@
1
- <div style="font-family: Arial, sans-serif; border: 1px solid #fab788; border-radius: 15px; padding: 25px; ">
2
-
3
- <h1 align="center" style="color: #fab788; border-bottom: 2px solid #3498db; padding-bottom: 10px;">Lavalink Client</h1>
4
- <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>
5
-
6
- <div align="center">
7
- <p>
8
- <img src="https://madewithlove.now.sh/at?heart=true&template=for-the-badge" alt="Made with love in Austria">
9
- <img alt="Made with TypeScript" src="https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white">
10
- </p>
11
- <p>
12
- <a href="https://www.npmjs.com/package/lavalink-client">
13
- <img src="https://img.shields.io/npm/v/lavalink-client.svg?maxAge=3600&style=for-the-badge&logo=npm&logoColor=red" alt="NPM Version" />
14
- </a>
15
- <a href="https://www.npmjs.com/package/lavalink-client">
16
- <img src="https://img.shields.io/npm/dt/lavalink-client.svg?maxAge=3600&style=for-the-badge&logo=npm&logoColor=red" alt="NPM Downloads" />
17
- </a>
18
- <a href="https://tomato6966.github.io/lavalink-client/">
19
- <img src="https://img.shields.io/badge/Documentation-%230288D1.svg?style=for-the-badge&logo=gitbook&logoColor=white" alt="Get Started Now">
20
- </a>
21
- </p>
22
- <p>
23
- <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>
24
- </p>
25
- </div>
26
-
27
- ***
28
-
29
- ## 🚀 Features
30
- - 💯 **Lavalink v4 Native:** Full support for Lavalink v4, including its powerful plugin ecosystem.
31
- - **Detailed Player-Destroy Reasons:** Understand precisely why a player was destroyed (e.g., channel deleted, bot disconnected).
32
- - **Flexible Queue Stores:** Use the default in-memory store or bring your own (Redis, databases, etc.) to sync queues across multiple processes.
33
- - 🎶 **Unresolved Tracks:** Supports unresolved track objects, fetching full data only when a track is about to play, saving API requests and resources.
34
- - 🎚️ **Built-in Filters & EQ:** Easy-to-use management for audio filters and equalizers.
35
- - 🔍 **Advanced Queue Filtering:** Search and filter tracks in the queue by title, author, duration, and more with powerful query options.
36
- - ⚙️ **Advanced Player Options:** Fine-tune player behavior for disconnects, empty queues, volume handling, and more.
37
- - 🛡️ **Lavalink-Side Validation:** Ensures you only use filters, plugins, and sources that your Lavalink node actually supports.
38
- - 🔒 **Client-Side Validation:** Whitelist and blacklist URLs or domains to prevent unwanted requests and protect your bot.
39
- - 🧑‍💻 **Developer-Friendly:** A memory-efficient design with a clean, intuitive API that mirrors Lavalink's own implementation.
40
- - 🤖 **Automated Handling:** Automatically handles track skipping on errors, voice channel deletions, server-wide mutes, and much more.
41
-
42
- ***
43
-
44
- ## 📦 Installation
45
-
46
- **Latest Stable Version: `v2.5.x`**
47
-
48
- <details>
49
- <summary><strong>👉 via NPM</strong></summary>
50
-
51
- ```bash
52
- # Stable (install release)
53
- npm install --save lavalink-client
54
-
55
- # Development (Install github dev-branch)
56
- npm install --save tomato6966/lavalink-client
57
- ```
58
-
59
- </details>
60
-
61
- <details>
62
- <summary><strong>👉 via YARN</strong></summary>
63
-
64
- ```bash
65
- # Stable (install release)
66
- yarn add lavalink-client
67
-
68
- # Development (Install github dev-branch)
69
- yarn add tomato6966/lavalink-client
70
- ```
71
-
72
- </details>
73
-
74
- <details>
75
- <summary><strong>👉 via BUN</strong></summary>
76
-
77
- ```bash
78
- # Stable (install release)
79
- bun add lavalink-client
80
-
81
- # Development (Install github dev-branch)
82
- bun add tomato6966/lavalink-client
83
- ```
84
-
85
- </details>
86
-
87
- <details>
88
- <summary><strong>👉 via pnpm</strong></summary>
89
-
90
- ```bash
91
- # Stable (install release)
92
- pnpm add lavalink-client
93
-
94
- # Development (Install github dev-branch)
95
- pnpm add tomato6966/lavalink-client
96
- ```
97
-
98
- </details>
99
-
100
- ## 📖 Documentation & Guides
101
-
102
- - **[Full Documentation](https://tomato6966.github.io/lavalink-client/)** - Your starting point for everything.
103
- - **[Manager Events](https://tomato6966.github.io/lavalink-client/extra/manager-events)** - Handle track, player, and general client events.
104
- - **[NodeManager Events](https://tomato6966.github.io/lavalink-client/extra/node-events)** - Manage node connections, errors, and logs.
105
- - **[Session Resuming Guide](https://tomato6966.github.io/lavalink-client/extra/resuming)** - Learn how to implement session resuming for seamless restarts.
106
-
107
- ***
108
-
109
- ## 💖 Used In
110
- This client powers various Discord bots:
111
- - **[Mivator](https://discord.gg/5dUb7M2qCj)** (Public Bot by @Tomato6966)
112
- - **[Betty](https://betty.bot/?utm_source=lavalink-client)** (Public Bot by @fb_sean)
113
- - **[Nero](https://betty.bot/?utm_source=lavalink-client)** (Public Bot by @fb_sean)
114
- - **Bots by Contributors:**
115
- - [Mintone](https://mintone.tech/) (@appujet)
116
- - [Stelle](https://github.com/Ganyu-Studios/stelle-music) (@EvilG-MC)
117
- - [Panais](https://panais.xyz/) (@LucasB25)
118
- - [Akyn](https://akynbot.vercel.app/) (@notdeltaxd)
119
- - [ARINO](https://site.arinoapp.qzz.io/) (@ryanwtf88)
120
- - [iHorizon](https://github.com/ihrz/ihrz) (@iHorizon)
121
- - **Bots Community (Users):**
122
- - [Soundy](https://github.com/idMJA/Soundy) (@idMJA)
123
- - [BeatBot](https://getbeatbot.vercel.app/) (@zenitsujs)
124
- - [Atom Music](https://top.gg/bot/1320469557411971165) (@sakshamyep)
125
- - [All Time Bot](https://top.gg/bot/1163027457671180418) (@PeterGamez)
126
- - [BeatDock](https://github.com/lazaroagomez/BeatDock) (@lazaroagomez)
127
-
128
- ***
129
-
130
- ## 🛠️ Configuration Examples
131
-
132
- ### Basic Setup
133
- A minimal example to get you started quickly.
134
- ```typescript
135
- import { LavalinkManager } from "lavalink-client";
136
- import { Client, GatewayIntentBits } from "discord.js"; // example for a discord bot
137
-
138
- // Extend the Client type to include the lavalink manager
139
- declare module "discord.js" {
140
- interface Client {
141
- lavalink: LavalinkManager;
142
- }
143
- }
144
-
145
- const client = new Client({
146
- intents: [
147
- GatewayIntentBits.Guilds,
148
- GatewayIntentBits.GuildVoiceStates,
149
- ]
150
- });
151
-
152
- client.lavalink = new LavalinkManager({
153
- nodes: [
154
- {
155
- authorization: "youshallnotpass", // The password for your Lavalink server
156
- host: "localhost",
157
- port: 2333,
158
- id: "Main Node",
159
- }
160
- ],
161
- // A function to send voice server updates to the Lavalink client
162
- sendToShard: (guildId, payload) => {
163
- const guild = client.guilds.cache.get(guildId);
164
- if (guild) guild.shard.send(payload);
165
- },
166
- autoSkip: true,
167
- client: {
168
- id: process.env.CLIENT_ID, // Your bot's user ID
169
- username: "MyBot",
170
- },
171
- });
172
-
173
- // Listen for the 'raw' event from discord.js and forward it
174
- client.on("raw", (d) => client.lavalink.sendRawData(d));
175
-
176
- client.on("ready", () => {
177
- console.log(`Logged in as ${client.user.tag}!`);
178
- // Initialize the Lavalink client
179
- client.lavalink.init({ ...client.user });
180
- });
181
-
182
- client.login(process.env.DISCORD_TOKEN);
183
- ```
184
-
185
- <details>
186
- <summary><strong>🔩 Complete Configuration Example (almost all Options)</strong></summary>
187
-
188
- ```typescript
189
- import { LavalinkManager, QueueChangesWatcher, QueueStoreManager, StoredQueue } from "lavalink-client";
190
- import { RedisClientType, createClient } from "redis";
191
- import { Client, GatewayIntentBits, User } from "discord.js";
192
-
193
- // It's recommended to extend the Client type
194
- declare module "discord.js" {
195
- interface Client {
196
- lavalink: LavalinkManager;
197
- redis: RedisClientType;
198
- }
199
- }
200
-
201
- const client = new Client({
202
- intents: [
203
- GatewayIntentBits.Guilds,
204
- GatewayIntentBits.GuildVoiceStates,
205
- ]
206
- });
207
-
208
- client.lavalink = new LavalinkManager({
209
- nodes: [
210
- {
211
- authorization: "youshallnotpass",
212
- host: "localhost",
213
- port: 2333,
214
- id: "testnode",
215
- secure: false, // Set to true for wss://
216
- retryAmount: 5,
217
- retryDelay: 10_000, // 10 seconds
218
- }
219
- ],
220
- sendToShard: (guildId, payload) => client.guilds.cache.get(guildId)?.shard?.send(payload),
221
- autoSkip: true, // automatically play the next song of the queue, on: trackend, trackerror, trackexception
222
- client: {
223
- id: process.env.CLIENT_ID,
224
- username: "TESTBOT",
225
- },
226
- playerOptions: {
227
- applyVolumeAsFilter: false,
228
- clientBasedPositionUpdateInterval: 50,
229
- defaultSearchPlatform: "ytmsearch",
230
- volumeDecrementer: 0.75,
231
- onDisconnect: {
232
- autoReconnect: true,
233
- destroyPlayer: false,
234
- },
235
- onEmptyQueue: {
236
- destroyAfterMs: 30_000,
237
- // function get's called onqueueempty, and if there are songs added to the queue, it continues playing. if not then not (autoplay functionality)
238
- // autoPlayFunction: async (player) => { /* ... */ },
239
- },
240
- useUnresolvedData: true,
241
- },
242
- queueOptions: {
243
- maxPreviousTracks: 10,
244
- queueStore: new MyCustomRedisStore(client.redis),
245
- queueChangesWatcher: new MyCustomQueueWatcher(client),
246
- },
247
- // Whitelist/Blacklist links or words
248
- linksAllowed: true,
249
- linksBlacklist: ["somebadsite.com"],
250
- linksWhitelist: [],
251
- advancedOptions: {
252
- debugOptions: {
253
- noAudio: false,
254
- playerDestroy: { dontThrowError: false, debugLog: false },
255
- }
256
- }
257
- });
258
-
259
- client.on("raw", d => client.lavalink.sendRawData(d));
260
- client.on("ready", () => client.lavalink.init({ ...client.user }));
261
-
262
- // Example Custom Redis Queue Store
263
- class MyCustomRedisStore implements QueueStoreManager {
264
- private redis: RedisClientType;
265
- constructor(redisClient: RedisClientType) {
266
- this.redis = redisClient;
267
- }
268
- private key(guildId: string) { return `lavalinkqueue_${guildId}`; }
269
- async get(guildId: string) { return await this.redis.get(this.key(guildId)); }
270
- async set(guildId: string, data: string) { return await this.redis.set(this.key(guildId), data); }
271
- async delete(guildId: string) { return await this.redis.del(this.key(guildId)); }
272
- async parse(data: string): Promise<Partial<StoredQueue>> { return JSON.parse(data); }
273
- stringify(data: Partial<StoredQueue>): string { return JSON.stringify(data); }
274
- }
275
-
276
- // Example Custom Queue Watcher
277
- class MyCustomQueueWatcher implements QueueChangesWatcher {
278
- private client: Client;
279
- constructor(client: Client) { this.client = client; }
280
- shuffled(guildId: string) { console.log(`Queue shuffled in guild: ${guildId}`); }
281
- tracksAdd(guildId: string, tracks: any[], position: number) { console.log(`${tracks.length} tracks added at position ${position} in guild: ${guildId}`); }
282
- tracksRemoved(guildId: string, tracks: any[], position: number) { console.log(`${tracks.length} tracks removed at position ${position} in guild: ${guildId}`); }
283
- }
284
- ```
285
-
286
- </details>
287
-
288
- ***
289
-
290
- ## 📢 Events
291
- Listen to events to create interactive and responsive logic.
292
-
293
- ### Lavalink Manager Events
294
- These events are emitted from the main `LavalinkManager` instance and relate to players and tracks.
295
-
296
- - `playerCreate (player)`
297
- - `playerDestroy (player, reason)`
298
- - `playerDisconnect (player, voiceChannelId)`
299
- - `playerMove (player, oldChannelId, newChannelId)`
300
- - `trackStart (player, track)`
301
- - `trackEnd (player, track)`
302
- - `trackStuck (player, track, payload)`
303
- - `trackError (player, track, payload)`
304
- - `queueEnd (player)`
305
-
306
- <details>
307
- <summary><strong>📢 Example for Manager-Event-Listeners</strong></summary>
308
-
309
- ```javascript
310
- // Example: Listening to a track start event
311
- client.lavalink.on("trackStart", (player, track) => {
312
- const channel = client.channels.cache.get(player.textChannelId);
313
- if(channel) channel.send(`Now playing: ${track.info.title}`);
314
- });
315
-
316
- // Example: Handling queue end
317
- client.lavalink.on("queueEnd", (player) => {
318
- const channel = client.channels.cache.get(player.textChannelId);
319
- if(channel) channel.send("The queue has finished. Add more songs!");
320
- player.destroy();
321
- });
322
- ```
323
- </details>
324
-
325
- ### Node Manager Events
326
- These events are emitted from `lavalink.nodeManager` and relate to the Lavalink node connections.
327
-
328
- - `create (node)`
329
- - `connect (node)`
330
- - `disconnect (node, reason)`
331
- - `reconnecting (node)`
332
- - `destroy (node)`
333
- - `error (node, error, payload)`
334
- - `resumed (node, payload, players)`
335
-
336
- <details>
337
- <summary><strong>📢 Example for Node-Event-Listeners</strong></summary>
338
-
339
- ```javascript
340
- // Example: Logging node connections and errors
341
- client.lavalink.nodeManager.on("connect", (node) => {
342
- console.log(`Node "${node.id}" connected!`);
343
- });
344
-
345
- client.lavalink.nodeManager.on("error", (node, error) => {
346
- console.error(`Node "${node.id}" encountered an error:`, error.message);
347
- });
348
- ```
349
- </details>
350
-
351
- ***
352
-
353
- ## 📚 Advanced How-To Guides
354
-
355
- ### How to Implement Session Resuming
356
- Resuming allows your music bot to continue playback even after a restart.
357
-
358
- 1. **Enable Resuming on the Node:** When a node connects, enable resuming with a timeout.
359
- 2. **Listen for the `resumed` Event:** This event fires on a successful reconnect, providing all player data from Lavalink.
360
- 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.
361
-
362
- > 💡 **For a complete, working example, see the [official test bot's implementation](https://github.com/Tomato6966/lavalink-client/blob/main/testBot/Utils/handleResuming.ts).**
363
-
364
- <details>
365
- <summary><strong>💡 Principle of how to enable **resuming**</strong></summary>
366
-
367
- ```javascript
368
- // 1. Enable resuming on connect
369
- client.lavalink.nodeManager.on("connect", (node) => {
370
- // Enable resuming for 5 minutes (300,000 ms)
371
- node.updateSession(true, 300_000);
372
- });
373
-
374
- // 2. Listen for the resumed event
375
- client.lavalink.nodeManager.on("resumed", async (node, payload, fetchedPlayers) => {
376
- console.log(`Node "${node.id}" successfully resumed with ${fetchedPlayers.length} players.`);
377
-
378
- for (const lavalinkData of fetchedPlayers) {
379
- // 3. Get your saved data (e.g., from Redis/DB)
380
- const savedData = await getFromDatabase(lavalinkData.guildId);
381
- if (!savedData || !lavalinkData.state.connected) {
382
- if(savedData) await deleteFromDatabase(lavalinkData.guildId);
383
- continue; // Skip if no saved data or Lavalink reports disconnected
384
- }
385
-
386
- // Re-create the player instance
387
- const player = client.lavalink.createPlayer({
388
- guildId: lavalinkData.guildId,
389
- voiceChannelId: savedData.voiceChannelId,
390
- textChannelId: savedData.textChannelId,
391
- // Important: Use the same node that was resumed
392
- node: node.id,
393
- // Set volume from Lavalink's data, accounting for the volume decrementer
394
- volume: lavalinkData.volume,
395
- selfDeaf: savedData.selfDeaf,
396
- });
397
-
398
- // Re-establish voice connection
399
- await player.connect();
400
-
401
- // Restore player state
402
- player.paused = lavalinkData.paused;
403
- player.lastPosition = lavalinkData.state.position;
404
- player.filterManager.data = lavalinkData.filters;
405
-
406
- // Restore the queue
407
- await player.queue.utils.sync(true, false); // Syncs with your QueueStore
408
-
409
- // Restore the current track
410
- if (lavalinkData.track) {
411
- player.queue.current = client.lavalink.utils.buildTrack(lavalinkData.track, savedData.requester);
412
- }
413
- }
414
- });
415
-
416
- // Persist player data on updates to use for resuming later
417
- client.lavalink.on("playerUpdate", (oldPlayer, newPlayer) => {
418
- saveToDatabase(newPlayer.toJSON());
419
- });
420
-
421
- // Clean up data when a player is permanently destroyed
422
- client.lavalink.on("playerDestroy", (player) => {
423
- deleteFromDatabase(player.guildId);
424
- });
425
- ```
426
- </details>
427
-
428
- ### How to Use Plugins
429
- Lavalink client supports most of the major lavalink-plugins.
430
- The client itself is - for beginner friendly reasons - atm not extendable (via plugins)
431
- 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.
432
-
433
- Some plugins require extra-parameters, such as flowerytts:
434
- Pass extra parameters to the search function to use plugin-specific features.
435
-
436
- <details>
437
- <summary><strong>How to use the flowerytts plugin</strong></summary>
438
-
439
- ```javascript
440
- // Example for flowertts plugin
441
- const query = interaction.options.getString("text");
442
- const voice = interaction.options.getString("voice"); // e.g., "MALE_1"
443
-
444
- const extraParams = new URLSearchParams();
445
- if (voice) extraParams.append(`voice`, voice);
446
-
447
- // All params for flowertts can be found here: https://flowery.pw/docs
448
- const response = await player.search(
449
- {
450
- query: `${query}`,
451
- // This is used by plugins like ftts to adjust the request
452
- extraQueryUrlParams: extraParams,
453
- source: "ftts" // Specify the plugin source
454
- },
455
- interaction.user // The requester
456
- );
457
-
458
- // Add the TTS track to the queue
459
- if (response.tracks.length > 0) {
460
- player.queue.add(response.tracks[0]);
461
- if (!player.playing) player.play();
462
- }
463
- ```
464
- </details>
465
-
466
- </div>
1
+ <div style="font-family: Arial, sans-serif; border: 1px solid #fab788; border-radius: 15px; padding: 25px; ">
2
+
3
+ <h1 align="center" style="color: #fab788; border-bottom: 2px solid #3498db; padding-bottom: 10px;">Lavalink Client</h1>
4
+ <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>
5
+
6
+ <div align="center">
7
+ <p>
8
+ <img src="https://madewithlove.now.sh/at?heart=true&template=for-the-badge" alt="Made with love in Austria">
9
+ <img alt="Made with TypeScript" src="https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white">
10
+ </p>
11
+ <p>
12
+ <a href="https://www.npmjs.com/package/lavalink-client">
13
+ <img src="https://img.shields.io/npm/v/lavalink-client.svg?maxAge=3600&style=for-the-badge&logo=npm&logoColor=red" alt="NPM Version" />
14
+ </a>
15
+ <a href="https://www.npmjs.com/package/lavalink-client">
16
+ <img src="https://img.shields.io/npm/dt/lavalink-client.svg?maxAge=3600&style=for-the-badge&logo=npm&logoColor=red" alt="NPM Downloads" />
17
+ </a>
18
+ <a href="https://tomato6966.github.io/lavalink-client/">
19
+ <img src="https://img.shields.io/badge/Documentation-%230288D1.svg?style=for-the-badge&logo=gitbook&logoColor=white" alt="Get Started Now">
20
+ </a>
21
+ </p>
22
+ <p>
23
+ <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>
24
+ </p>
25
+ </div>
26
+
27
+ ---
28
+
29
+ ## 🚀 Features
30
+
31
+ - 💯 **Lavalink v4 Native:** Full support for Lavalink v4, including its powerful plugin ecosystem.
32
+ - **Detailed Player-Destroy Reasons:** Understand precisely why a player was destroyed (e.g., channel deleted, bot disconnected).
33
+ - **Flexible Queue Stores:** Use the default in-memory store or bring your own (Redis, databases, etc.) to sync queues across multiple processes.
34
+ - 🎶 **Unresolved Tracks:** Supports unresolved track objects, fetching full data only when a track is about to play, saving API requests and resources.
35
+ - 🎚️ **Built-in Filters & EQ:** Easy-to-use management for audio filters and equalizers.
36
+ - 🔍 **Advanced Queue Filtering:** Search and filter tracks in the queue by title, author, duration, and more with powerful query options.
37
+ - ⚙️ **Advanced Player Options:** Fine-tune player behavior for disconnects, empty queues, volume handling, and more.
38
+ - 🛡️ **Lavalink-Side Validation:** Ensures you only use filters, plugins, and sources that your Lavalink node actually supports.
39
+ - 🔒 **Client-Side Validation:** Whitelist and blacklist URLs or domains to prevent unwanted requests and protect your bot.
40
+ - 🧑‍💻 **Developer-Friendly:** A memory-efficient design with a clean, intuitive API that mirrors Lavalink's own implementation.
41
+ - 🤖 **Automated Handling:** Automatically handles track skipping on errors, voice channel deletions, server-wide mutes, and much more.
42
+
43
+ ---
44
+
45
+ ## 📦 Installation
46
+
47
+ **Latest Stable Version: `v2.5.x`**
48
+
49
+ <details>
50
+ <summary><strong>👉 via NPM</strong></summary>
51
+
52
+ ```bash
53
+ # Stable (install release)
54
+ npm install --save lavalink-client
55
+
56
+ # Development (Install github dev-branch)
57
+ npm install --save tomato6966/lavalink-client
58
+ ```
59
+
60
+ </details>
61
+
62
+ <details>
63
+ <summary><strong>👉 via YARN</strong></summary>
64
+
65
+ ```bash
66
+ # Stable (install release)
67
+ yarn add lavalink-client
68
+
69
+ # Development (Install github dev-branch)
70
+ yarn add tomato6966/lavalink-client
71
+ ```
72
+
73
+ </details>
74
+
75
+ <details>
76
+ <summary><strong>👉 via BUN</strong></summary>
77
+
78
+ ```bash
79
+ # Stable (install release)
80
+ bun add lavalink-client
81
+
82
+ # Development (Install github dev-branch)
83
+ bun add tomato6966/lavalink-client
84
+ ```
85
+
86
+ </details>
87
+
88
+ <details>
89
+ <summary><strong>👉 via pnpm</strong></summary>
90
+
91
+ ```bash
92
+ # Stable (install release)
93
+ pnpm add lavalink-client
94
+
95
+ # Development (Install github dev-branch)
96
+ pnpm add tomato6966/lavalink-client
97
+ ```
98
+
99
+ </details>
100
+
101
+ ## 📖 Documentation & Guides
102
+
103
+ - **[Full Documentation](https://tomato6966.github.io/lavalink-client/)** - Your starting point for everything.
104
+ - **[Manager Events](https://tomato6966.github.io/lavalink-client/extra/manager-events)** - Handle track, player, and general client events.
105
+ - **[NodeManager Events](https://tomato6966.github.io/lavalink-client/extra/node-events)** - Manage node connections, errors, and logs.
106
+ - **[Session Resuming Guide](https://tomato6966.github.io/lavalink-client/extra/resuming)** - Learn how to implement session resuming for seamless restarts.
107
+
108
+ ---
109
+
110
+ # Node Link
111
+
112
+ 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:
113
+
114
+ ```ts
115
+ nodeOptions.nodeType = "NodeLink";
116
+ ```
117
+
118
+ this can be done directly when creating the node in the lavalinkmanager.
119
+
120
+ ```ts
121
+ client.lavalink = new LavalinkManager({
122
+ nodes: [
123
+ {
124
+ host: "localhost",
125
+ nodeType: "NodeLink",
126
+ },
127
+ ],
128
+ });
129
+ // or here if you need a bigger example.
130
+ client.lavalink = new LavalinkManager({
131
+ nodes: [
132
+ {
133
+ authorization: "youshallnotpass", // The password for your Lavalink server
134
+ host: "localhost",
135
+ port: 2333,
136
+ id: "Main Node",
137
+ // set to nodeLink
138
+ nodeType: "NodeLink",
139
+ },
140
+ ],
141
+ // A function to send voice server updates to the Lavalink client
142
+ sendToShard: (guildId, payload) => {
143
+ const guild = client.guilds.cache.get(guildId);
144
+ if (guild) guild.shard.send(payload);
145
+ },
146
+ autoSkip: true,
147
+ client: {
148
+ id: process.env.CLIENT_ID, // Your bot's user ID
149
+ username: "MyBot",
150
+ },
151
+ });
152
+ ```
153
+
154
+ Now if you want to use NodeLink specific functions, you can use the type assertion checker function:
155
+
156
+ ```ts
157
+ if (node.isNodeLink()) {
158
+ // node is now typed as NodeLink
159
+ node.addMixerLayer();
160
+ } else if (node.isLavalinkNode()) {
161
+ // node is now typed as LavalinkNode
162
+ } else {
163
+ // node is now typed as whatever it is..
164
+ }
165
+ ```
166
+
167
+ or you have to assert the type...
168
+
169
+ ```
170
+ const node = client.lavalink.lavalinkManager.getNode("id") as NodeLinkNode;
171
+ node.addMixerLayer()
172
+ ```
173
+
174
+ ### NodeLink Specific Methods
175
+
176
+ - **`node.getYoutubeOAUTH(refreshToken)`**: Exchange a Refresh Token for an Access Token. [Docs](https://nodelink.js.org/docs/api/nodelink-features#oauth)
177
+ - **`node.updateYoutubeOAUTH(refreshToken)`**: Update the OAUTH token. [Docs](https://nodelink.js.org/docs/api/nodelink-features#oauth)
178
+ - **`node.getChapters(player, track?)`**: Retrieve Chapters of Youtube Videos. [Docs](https://nodelink.js.org/docs/api/nodelink-features#loadchapters)
179
+ - **`node.nodeLinkLyrics(player, track?, language?)`**: Retrieve Lyrics of Youtube Videos. [Docs](https://nodelink.js.org/docs/api/nodelink-features#lyrics--chapters)
180
+ - **`node.getDirectStream(track)`**: Stream audio directly from NodeLink. [Docs](https://nodelink.js.org/docs/api/nodelink-features#direct-streaming)
181
+ - **`node.listMixerLayers(player)`**: Retrieves a list of currently active mix layers. [Docs](https://nodelink.js.org/docs/api/rest#get-active-mixes)
182
+ - **`node.addMixerLayer(player, track, volume)`**: Adds a new audio track to be mixed. [Docs](https://nodelink.js.org/docs/api/rest#add-mix-layer)
183
+ - **`node.removeMixerLayer(player, mixId)`**: Removes a specific mix layer. [Docs](https://nodelink.js.org/docs/api/rest#remove-mix-layer)
184
+ - **`node.updateMixerLayerVolume(player, mixId, volume)`**: Updates the volume of a specific mix layer. [Docs](https://nodelink.js.org/docs/api/rest#update-mix-volume)
185
+ - **`node.changeAudioTrackLanguage(player, language_audioTrackId)`**: Changes the current language of the audio. [Docs](https://nodelink.js.org/docs/api/nodelink-features#additional-filters)
186
+ - **`node.updateYoutubeConfig(refreshToken?, visitorData?)`**: Updates the YouTube configuration. [Docs](https://nodelink.js.org/docs/api/nodelink-features#update-config)
187
+ - **`node.getYoutubeConfig(validate?)`**: Gets the YouTube configuration.
188
+ - **`node.getConnectionMetrics()`**: Get connection metrics. [Docs](https://nodelink.js.org/docs/api/rest#node-information)
189
+ - **`node.loadDirectStream(track, volume, position, filters)`**: Stream raw PCM audio. [Docs](https://nodelink.js.org/docs/api/nodelink-features#loadstream)
190
+
191
+ ### NodeLink Specififc Events?
192
+
193
+ ```ts
194
+ // NodeLink specific events
195
+ client.lavalink.nodeManager.on("nodeLinkEvent", (node, eventName, player, track, payload) => {
196
+ switch (eventName) {
197
+ // -------------Player LifeCycle Events-------------
198
+ // https://nodelink.js.org/docs/api/websocket#playercreatedevent
199
+ case "PlayerCreatedEvent":
200
+ {
201
+ // { "guildId": "987654321098765432", "track": null, "paused": false, "volume": 100 }
202
+ const playerInfo = payload.player;
203
+ console.log(`Player created in guildId: ${playerInfo.guildId}`);
204
+ }
205
+ break;
206
+ // https://nodelink.js.org/docs/api/websocket#playerdestroyedevent
207
+ case "PlayerDestroyedEvent":
208
+ {
209
+ // "987654321098765432"
210
+ const playerInfo = payload.guildId;
211
+ console.log(`Player destroyed in guildId: ${playerInfo.guildId}`);
212
+ }
213
+ break;
214
+ // https://nodelink.js.org/docs/api/websocket#playerconnectedevent
215
+ case "PlayerConnectedEvent":
216
+ {
217
+ // { "sessionId": "abc", "token": "token", "endpoint": "us-central123.discord.media", "channelId": "123456789012345678" }
218
+ const playerInfo = payload.voice;
219
+ console.log(`Player connected in guildId: ${playerInfo.guildId}`);
220
+ }
221
+ break;
222
+ // https://nodelink.js.org/docs/api/websocket#playerreconnectingevent
223
+ case "PlayerReconnectingEvent":
224
+ {
225
+ // { "sessionId": "abc", "token": "token", "endpoint": "us-central123.discord.media", "channelId": "123456789012345678" }
226
+ const playerInfo = payload.voice;
227
+ console.log(`Player reconnecting in guildId: ${playerInfo.guildId}`);
228
+ }
229
+ break;
230
+
231
+ // -------------Player State Events-------------
232
+ // https://nodelink.js.org/docs/api/websocket#volumechangedevent
233
+ case "VolumeChangedEvent":
234
+ {
235
+ // "guildId": "987654321098765432",
236
+ // "volume": 100
237
+ const { guildId, volume } = payload;
238
+ console.log(`Player volume changed in guildId: ${guildId} to ${volume}`);
239
+ }
240
+ break;
241
+ // https://nodelink.js.org/docs/api/websocket#filterschangedevent
242
+ case "FiltersChangedEvent":
243
+ {
244
+ // { ...Filtersdata... }
245
+ const { guildId, filters } = payload;
246
+ console.log(`Player filters changed in guildId: ${guildId}, new Data: `, filters);
247
+ }
248
+ break;
249
+ // https://nodelink.js.org/docs/api/websocket#seekevent
250
+ case "SeekEvent":
251
+ {
252
+ // "guildId": "987654321098765432",
253
+ // "position": 10000,
254
+ const { guildId, position } = payload;
255
+ console.log(`Player seeked in guildId: ${guildId}, new position: ${position}`);
256
+ }
257
+ break;
258
+ // https://nodelink.js.org/docs/api/websocket#pauseevent
259
+ case "PauseEvent":
260
+ {
261
+ // "guildId": "987654321098765432",
262
+ // "paused": true
263
+ const { guildId, paused } = payload;
264
+ console.log(`Player paused in guildId: ${guildId}, paused true/false: ${paused}`);
265
+ }
266
+ break;
267
+ // https://nodelink.js.org/docs/api/websocket#connectionstatusevent
268
+ case "ConnectionStatusEvent":
269
+ {
270
+ // "guildId": "987654321098765432",
271
+ // "status": "CONNECTED"
272
+ // "metrics": { ... }
273
+ const { guildId, status, metrics } = payload;
274
+ console.log(
275
+ `Player connection status changed in guildId: ${guildId}, status: ${status}, metrics: `,
276
+ metrics,
277
+ );
278
+ }
279
+ break;
280
+
281
+ // -------------LYRICS EVENTS-------------
282
+ // https://nodelink.js.org/docs/api/websocket#lyrics-events
283
+ case "LyricsFoundEvent":
284
+ {
285
+ // "guildId": "987654321098765432",
286
+ // "lyrics": "..."
287
+ const { guildId, lyrics } = payload;
288
+ console.log(`Lyrics found in guildId: ${guildId}, lyrics: `, lyrics);
289
+ }
290
+ break;
291
+ // https://nodelink.js.org/docs/api/websocket#lyricslineevent
292
+ case "LyricsLineEvent":
293
+ {
294
+ // "guildId": "987654321098765432",
295
+ // "lineIndex": 0,
296
+ // "line": "..."
297
+ const { guildId, lineIndex, line } = payload;
298
+ console.log(`Lyrics line in guildId: ${guildId}, lineIndex: ${lineIndex}, line: `, line);
299
+ }
300
+ break;
301
+ // https://nodelink.js.org/docs/api/websocket#lyricsnotfoundevent
302
+ case "LyricsNotFoundEvent":
303
+ {
304
+ // "guildId": "987654321098765432",
305
+ const { guildId } = payload;
306
+ console.log(`Lyrics not found in guildId: ${guildId}`);
307
+ }
308
+ break;
309
+
310
+ // -------------AUDIO MIXER EVENTS-------------
311
+ // https://nodelink.js.org/docs/api/websocket#mixstartedevent
312
+ case "MixStartedEvent":
313
+ {
314
+ // "guildId": "987654321098765432",
315
+ // "mixId": "123456789012345678"
316
+ // "volume": 0.8,
317
+ // "track": { ...TrackData... }
318
+ const { guildId, mixId, volume, track } = payload.player;
319
+ console.log(
320
+ `Player mix started in guildId: ${guildId}, mixId: ${mixId}, volume: ${volume}, track: `,
321
+ track,
322
+ );
323
+ }
324
+ break;
325
+ // https://nodelink.js.org/docs/api/websocket#mixendedevent
326
+ case "MixEndedEvent":
327
+ {
328
+ // "guildId": "987654321098765432",
329
+ // "mixId": "123456789012345678",
330
+ // "reason": "USER_STOPPED"
331
+ const { guildId, mixId, reason } = payload;
332
+ console.log(`Player mix ended in guildId: ${guildId}, mixId: ${mixId}, reason: ${reason}`);
333
+ }
334
+ break;
335
+ }
336
+ });
337
+ ```
338
+
339
+ ---
340
+
341
+ ## 💖 Used In
342
+
343
+ This client powers various Discord bots:
344
+
345
+ - **[Mivator](https://discord.gg/5dUb7M2qCj)** (Public Bot by @Tomato6966)
346
+ - **[Betty](https://betty.bot/?utm_source=lavalink-client)** (Public Bot by @fb_sean)
347
+ - **[Nero](https://betty.bot/?utm_source=lavalink-client)** (Public Bot by @fb_sean)
348
+ - **Bots by Contributors:**
349
+ - [Mintone](https://mintone.tech/) (@appujet)
350
+ - [Stelle](https://github.com/Ganyu-Studios/stelle-music) (@EvilG-MC)
351
+ - [Panais](https://panais.xyz/) (@LucasB25)
352
+ - [Akyn](https://akynbot.vercel.app/) (@notdeltaxd)
353
+ - [ARINO](https://site.arinoapp.qzz.io/) (@ryanwtf88)
354
+ - [iHorizon](https://github.com/ihrz/ihrz) (@iHorizon)
355
+ - **Bots By Community (Users):**
356
+ - [Soundy](https://github.com/idMJA/Soundy) (@idMJA)
357
+ - [BeatBot](https://getbeatbot.vercel.app/) (@zenitsujs)
358
+ - [Atom Music](https://top.gg/bot/1320469557411971165) (@sakshamyep)
359
+ - [All Time Bot](https://top.gg/bot/1163027457671180418) (@PeterGamez)
360
+ - [BeatDock](https://github.com/lazaroagomez/BeatDock) (@lazaroagomez)
361
+ - [Nazha](https://top.gg/bot/1124681788070055967) (@Nazha-Team)
362
+
363
+ ---
364
+
365
+ ## 🛠️ Configuration Examples
366
+
367
+ ### Basic Setup
368
+
369
+ A minimal example to get you started quickly.
370
+
371
+ ```typescript
372
+ import { LavalinkManager } from "lavalink-client";
373
+ import { Client, GatewayIntentBits } from "discord.js"; // example for a discord bot
374
+
375
+ // Extend the Client type to include the lavalink manager
376
+ declare module "discord.js" {
377
+ interface Client {
378
+ lavalink: LavalinkManager;
379
+ }
380
+ }
381
+
382
+ const client = new Client({
383
+ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
384
+ });
385
+
386
+ client.lavalink = new LavalinkManager({
387
+ nodes: [
388
+ {
389
+ authorization: "youshallnotpass", // The password for your Lavalink server
390
+ host: "localhost",
391
+ port: 2333,
392
+ id: "Main Node",
393
+ },
394
+ ],
395
+ // A function to send voice server updates to the Lavalink client
396
+ sendToShard: (guildId, payload) => {
397
+ const guild = client.guilds.cache.get(guildId);
398
+ if (guild) guild.shard.send(payload);
399
+ },
400
+ autoSkip: true,
401
+ client: {
402
+ id: process.env.CLIENT_ID, // Your bot's user ID
403
+ username: "MyBot",
404
+ },
405
+ });
406
+
407
+ // Listen for the 'raw' event from discord.js and forward it
408
+ client.on("raw", (d) => client.lavalink.sendRawData(d));
409
+
410
+ client.on("ready", () => {
411
+ console.log(`Logged in as ${client.user.tag}!`);
412
+ // Initialize the Lavalink client
413
+ client.lavalink.init({ ...client.user });
414
+ });
415
+
416
+ client.login(process.env.DISCORD_TOKEN);
417
+ ```
418
+
419
+ <details>
420
+ <summary><strong>🔩 Complete Configuration Example (almost all Options)</strong></summary>
421
+
422
+ ```typescript
423
+ import { LavalinkManager, QueueChangesWatcher, QueueStoreManager, StoredQueue } from "lavalink-client";
424
+ import { RedisClientType, createClient } from "redis";
425
+ import { Client, GatewayIntentBits, User } from "discord.js";
426
+
427
+ // It's recommended to extend the Client type
428
+ declare module "discord.js" {
429
+ interface Client {
430
+ lavalink: LavalinkManager;
431
+ redis: RedisClientType;
432
+ }
433
+ }
434
+
435
+ const client = new Client({
436
+ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildVoiceStates],
437
+ });
438
+
439
+ client.lavalink = new LavalinkManager({
440
+ nodes: [
441
+ {
442
+ authorization: "youshallnotpass",
443
+ host: "localhost",
444
+ port: 2333,
445
+ id: "testnode",
446
+ secure: false, // Set to true for wss://
447
+ retryAmount: 5,
448
+ retryDelay: 10_000, // 10 seconds
449
+ },
450
+ ],
451
+ sendToShard: (guildId, payload) => client.guilds.cache.get(guildId)?.shard?.send(payload),
452
+ autoSkip: true, // automatically play the next song of the queue, on: trackend, trackerror, trackexception
453
+ client: {
454
+ id: process.env.CLIENT_ID,
455
+ username: "TESTBOT",
456
+ },
457
+ playerOptions: {
458
+ applyVolumeAsFilter: false,
459
+ clientBasedPositionUpdateInterval: 50,
460
+ defaultSearchPlatform: "ytmsearch",
461
+ volumeDecrementer: 0.75,
462
+ onDisconnect: {
463
+ autoReconnect: true,
464
+ destroyPlayer: false,
465
+ },
466
+ onEmptyQueue: {
467
+ destroyAfterMs: 30_000,
468
+ // function get's called onqueueempty, and if there are songs added to the queue, it continues playing. if not then not (autoplay functionality)
469
+ // autoPlayFunction: async (player) => { /* ... */ },
470
+ },
471
+ useUnresolvedData: true,
472
+ },
473
+ queueOptions: {
474
+ maxPreviousTracks: 10,
475
+ queueStore: new MyCustomRedisStore(client.redis),
476
+ queueChangesWatcher: new MyCustomQueueWatcher(client),
477
+ },
478
+ // Whitelist/Blacklist links or words
479
+ linksAllowed: true,
480
+ linksBlacklist: ["somebadsite.com"],
481
+ linksWhitelist: [],
482
+ advancedOptions: {
483
+ debugOptions: {
484
+ noAudio: false,
485
+ playerDestroy: { dontThrowError: false, debugLog: false },
486
+ },
487
+ },
488
+ });
489
+
490
+ client.on("raw", (d) => client.lavalink.sendRawData(d));
491
+ client.on("ready", () => client.lavalink.init({ ...client.user }));
492
+
493
+ // Example Custom Redis Queue Store
494
+ class MyCustomRedisStore implements QueueStoreManager {
495
+ private redis: RedisClientType;
496
+ constructor(redisClient: RedisClientType) {
497
+ this.redis = redisClient;
498
+ }
499
+ private key(guildId: string) {
500
+ return `lavalinkqueue_${guildId}`;
501
+ }
502
+ async get(guildId: string) {
503
+ return await this.redis.get(this.key(guildId));
504
+ }
505
+ async set(guildId: string, data: string) {
506
+ return await this.redis.set(this.key(guildId), data);
507
+ }
508
+ async delete(guildId: string) {
509
+ return await this.redis.del(this.key(guildId));
510
+ }
511
+ async parse(data: string): Promise<Partial<StoredQueue>> {
512
+ return JSON.parse(data);
513
+ }
514
+ stringify(data: Partial<StoredQueue>): string {
515
+ return JSON.stringify(data);
516
+ }
517
+ }
518
+
519
+ // Example Custom Queue Watcher
520
+ class MyCustomQueueWatcher implements QueueChangesWatcher {
521
+ private client: Client;
522
+ constructor(client: Client) {
523
+ this.client = client;
524
+ }
525
+ shuffled(guildId: string) {
526
+ console.log(`Queue shuffled in guild: ${guildId}`);
527
+ }
528
+ tracksAdd(guildId: string, tracks: any[], position: number) {
529
+ console.log(`${tracks.length} tracks added at position ${position} in guild: ${guildId}`);
530
+ }
531
+ tracksRemoved(guildId: string, tracks: any[], position: number) {
532
+ console.log(`${tracks.length} tracks removed at position ${position} in guild: ${guildId}`);
533
+ }
534
+ }
535
+ ```
536
+
537
+ </details>
538
+
539
+ ---
540
+
541
+ ## 📢 Events
542
+
543
+ Listen to events to create interactive and responsive logic.
544
+
545
+ ### Lavalink Manager Events
546
+
547
+ These events are emitted from the main `LavalinkManager` instance and relate to players and tracks.
548
+
549
+ - `playerCreate (player)`
550
+ - `playerDestroy (player, reason)`
551
+ - `playerDisconnect (player, voiceChannelId)`
552
+ - `playerMove (player, oldChannelId, newChannelId)`
553
+ - `trackStart (player, track)`
554
+ - `trackEnd (player, track)`
555
+ - `trackStuck (player, track, payload)`
556
+ - `trackError (player, track, payload)`
557
+ - `queueEnd (player)`
558
+
559
+ <details>
560
+ <summary><strong>📢 Example for Manager-Event-Listeners</strong></summary>
561
+
562
+ ```javascript
563
+ // Example: Listening to a track start event
564
+ client.lavalink.on("trackStart", (player, track) => {
565
+ const channel = client.channels.cache.get(player.textChannelId);
566
+ if (channel) channel.send(`Now playing: ${track.info.title}`);
567
+ });
568
+
569
+ // Example: Handling queue end
570
+ client.lavalink.on("queueEnd", (player) => {
571
+ const channel = client.channels.cache.get(player.textChannelId);
572
+ if (channel) channel.send("The queue has finished. Add more songs!");
573
+ player.destroy();
574
+ });
575
+ ```
576
+
577
+ </details>
578
+
579
+ ### Node Manager Events
580
+
581
+ These events are emitted from `lavalink.nodeManager` and relate to the Lavalink node connections.
582
+
583
+ - `create (node)`
584
+ - `connect (node)`
585
+ - `disconnect (node, reason)`
586
+ - `reconnecting (node)`
587
+ - `destroy (node)`
588
+ - `error (node, error, payload)`
589
+ - `resumed (node, payload, players)`
590
+
591
+ <details>
592
+ <summary><strong>📢 Example for Node-Event-Listeners</strong></summary>
593
+
594
+ ```javascript
595
+ // Example: Logging node connections and errors
596
+ client.lavalink.nodeManager.on("connect", (node) => {
597
+ console.log(`Node "${node.id}" connected!`);
598
+ });
599
+
600
+ client.lavalink.nodeManager.on("error", (node, error) => {
601
+ console.error(`Node "${node.id}" encountered an error:`, error.message);
602
+ });
603
+ ```
604
+
605
+ </details>
606
+
607
+ ---
608
+
609
+ ## 📚 Advanced How-To Guides
610
+
611
+ ### How to Implement Session Resuming
612
+
613
+ Resuming allows your music bot to continue playback even after a restart.
614
+
615
+ 1. **Enable Resuming on the Node:** When a node connects, enable resuming with a timeout.
616
+ 2. **Listen for the `resumed` Event:** This event fires on a successful reconnect, providing all player data from Lavalink.
617
+ 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.
618
+
619
+ > 💡 **For a complete, working example, see the [official test bot's implementation](https://github.com/Tomato6966/lavalink-client/blob/main/testBot/Utils/handleResuming.ts).**
620
+
621
+ <details>
622
+ <summary><strong>💡 Principle of how to enable **resuming**</strong></summary>
623
+
624
+ ```javascript
625
+ // 1. Enable resuming on connect
626
+ client.lavalink.nodeManager.on("connect", (node) => {
627
+ // Enable resuming for 5 minutes (300,000 ms)
628
+ node.updateSession(true, 300_000);
629
+ });
630
+
631
+ // 2. Listen for the resumed event
632
+ client.lavalink.nodeManager.on("resumed", async (node, payload, fetchedPlayers) => {
633
+ console.log(`Node "${node.id}" successfully resumed with ${fetchedPlayers.length} players.`);
634
+
635
+ for (const lavalinkData of fetchedPlayers) {
636
+ // 3. Get your saved data (e.g., from Redis/DB)
637
+ const savedData = await getFromDatabase(lavalinkData.guildId);
638
+ if (!savedData || !lavalinkData.state.connected) {
639
+ if (savedData) await deleteFromDatabase(lavalinkData.guildId);
640
+ continue; // Skip if no saved data or Lavalink reports disconnected
641
+ }
642
+
643
+ // Re-create the player instance
644
+ const player = client.lavalink.createPlayer({
645
+ guildId: lavalinkData.guildId,
646
+ voiceChannelId: savedData.voiceChannelId,
647
+ textChannelId: savedData.textChannelId,
648
+ // Important: Use the same node that was resumed
649
+ node: node.id,
650
+ // Set volume from Lavalink's data, accounting for the volume decrementer
651
+ volume: lavalinkData.volume,
652
+ selfDeaf: savedData.selfDeaf,
653
+ });
654
+
655
+ // Re-establish voice connection
656
+ await player.connect();
657
+
658
+ // Restore player state
659
+ player.paused = lavalinkData.paused;
660
+ player.lastPosition = lavalinkData.state.position;
661
+ player.filterManager.data = lavalinkData.filters;
662
+
663
+ // Restore the queue
664
+ await player.queue.utils.sync(true, false); // Syncs with your QueueStore
665
+
666
+ // Restore the current track
667
+ if (lavalinkData.track) {
668
+ player.queue.current = client.lavalink.utils.buildTrack(lavalinkData.track, savedData.requester);
669
+ }
670
+ }
671
+ });
672
+
673
+ // Persist player data on updates to use for resuming later
674
+ client.lavalink.on("playerUpdate", (oldPlayer, newPlayer) => {
675
+ saveToDatabase(newPlayer.toJSON());
676
+ });
677
+
678
+ // Clean up data when a player is permanently destroyed
679
+ client.lavalink.on("playerDestroy", (player) => {
680
+ deleteFromDatabase(player.guildId);
681
+ });
682
+ ```
683
+
684
+ </details>
685
+
686
+ ### How to Use Plugins
687
+
688
+ Lavalink client supports most of the major lavalink-plugins.
689
+ The client itself is - for beginner friendly reasons - atm not extendable (via plugins)
690
+ 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.
691
+
692
+ Some plugins require extra-parameters, such as flowerytts:
693
+ Pass extra parameters to the search function to use plugin-specific features.
694
+
695
+ <details>
696
+ <summary><strong>How to use the flowerytts plugin</strong></summary>
697
+
698
+ ```javascript
699
+ // Example for flowertts plugin
700
+ const query = interaction.options.getString("text");
701
+ const voice = interaction.options.getString("voice"); // e.g., "MALE_1"
702
+
703
+ const extraParams = new URLSearchParams();
704
+ if (voice) extraParams.append(`voice`, voice);
705
+
706
+ // All params for flowertts can be found here: https://flowery.pw/docs
707
+ const response = await player.search(
708
+ {
709
+ query: `${query}`,
710
+ // This is used by plugins like ftts to adjust the request
711
+ extraQueryUrlParams: extraParams,
712
+ source: "ftts", // Specify the plugin source
713
+ },
714
+ interaction.user, // The requester
715
+ );
716
+
717
+ // Add the TTS track to the queue
718
+ if (response.tracks.length > 0) {
719
+ player.queue.add(response.tracks[0]);
720
+ if (!player.playing) player.play();
721
+ }
722
+ ```
723
+
724
+ </details>
725
+
726
+ </div>