lavalink-client 2.8.0 → 2.9.1
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.
- package/README.md +726 -518
- package/dist/index.d.mts +2427 -1893
- package/dist/index.d.ts +2427 -1893
- package/dist/index.js +1776 -566
- package/dist/index.mjs +1773 -566
- package/package.json +79 -81
package/README.md
CHANGED
|
@@ -1,518 +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
|
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
- **[
|
|
104
|
-
- **[
|
|
105
|
-
- **[
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
nodeOptions.
|
|
116
|
-
```
|
|
117
|
-
|
|
118
|
-
this can be done directly when creating the node in the lavalinkmanager.
|
|
119
|
-
|
|
120
|
-
```ts
|
|
121
|
-
client.lavalink = new LavalinkManager({
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
-
|
|
347
|
-
-
|
|
348
|
-
-
|
|
349
|
-
-
|
|
350
|
-
-
|
|
351
|
-
-
|
|
352
|
-
-
|
|
353
|
-
-
|
|
354
|
-
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
```
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
{
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
);
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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>
|