ryanlink 1.0.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.
Files changed (60) hide show
  1. package/LICENSE +37 -0
  2. package/README.md +455 -0
  3. package/dist/index.d.mts +1335 -0
  4. package/dist/index.d.ts +1335 -0
  5. package/dist/index.js +4694 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/index.mjs +4604 -0
  8. package/dist/index.mjs.map +1 -0
  9. package/package.json +82 -0
  10. package/src/audio/AudioFilters.ts +316 -0
  11. package/src/audio/AudioQueue.ts +782 -0
  12. package/src/audio/AudioTrack.ts +242 -0
  13. package/src/audio/QueueController.ts +252 -0
  14. package/src/audio/TrackCollection.ts +138 -0
  15. package/src/audio/index.ts +9 -0
  16. package/src/config/defaults.ts +223 -0
  17. package/src/config/endpoints.ts +99 -0
  18. package/src/config/index.ts +9 -0
  19. package/src/config/patterns.ts +55 -0
  20. package/src/config/presets.ts +400 -0
  21. package/src/config/symbols.ts +31 -0
  22. package/src/core/PluginSystem.ts +50 -0
  23. package/src/core/RyanlinkPlayer.ts +403 -0
  24. package/src/core/index.ts +6 -0
  25. package/src/extensions/AutoplayExtension.ts +283 -0
  26. package/src/extensions/FairPlayExtension.ts +154 -0
  27. package/src/extensions/LyricsExtension.ts +187 -0
  28. package/src/extensions/PersistenceExtension.ts +182 -0
  29. package/src/extensions/SponsorBlockExtension.ts +81 -0
  30. package/src/extensions/index.ts +9 -0
  31. package/src/index.ts +19 -0
  32. package/src/lavalink/ConnectionPool.ts +326 -0
  33. package/src/lavalink/HttpClient.ts +316 -0
  34. package/src/lavalink/LavalinkConnection.ts +409 -0
  35. package/src/lavalink/index.ts +7 -0
  36. package/src/metadata.ts +88 -0
  37. package/src/types/api/Rest.ts +949 -0
  38. package/src/types/api/Websocket.ts +463 -0
  39. package/src/types/api/index.ts +6 -0
  40. package/src/types/audio/FilterManager.ts +29 -0
  41. package/src/types/audio/Queue.ts +4 -0
  42. package/src/types/audio/QueueManager.ts +30 -0
  43. package/src/types/audio/index.ts +7 -0
  44. package/src/types/common.ts +63 -0
  45. package/src/types/core/Player.ts +322 -0
  46. package/src/types/core/index.ts +5 -0
  47. package/src/types/index.ts +6 -0
  48. package/src/types/lavalink/Node.ts +173 -0
  49. package/src/types/lavalink/NodeManager.ts +34 -0
  50. package/src/types/lavalink/REST.ts +144 -0
  51. package/src/types/lavalink/index.ts +32 -0
  52. package/src/types/voice/VoiceManager.ts +176 -0
  53. package/src/types/voice/index.ts +5 -0
  54. package/src/utils/helpers.ts +169 -0
  55. package/src/utils/index.ts +6 -0
  56. package/src/utils/validators.ts +184 -0
  57. package/src/voice/RegionSelector.ts +184 -0
  58. package/src/voice/VoiceConnection.ts +451 -0
  59. package/src/voice/VoiceSession.ts +297 -0
  60. package/src/voice/index.ts +7 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,4604 @@
1
+ import { readFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { URL } from 'url';
4
+ import { EventEmitter, once } from 'events';
5
+ import { clearTimeout as clearTimeout$1, setTimeout as setTimeout$1 } from 'timers';
6
+ import { WebSocket } from 'ws';
7
+ import { setTimeout as setTimeout$2 } from 'timers/promises';
8
+
9
+ /* Ryanlink v1.0.0 - Modern Lavalink Client | Apache-2.0 License | https://github.com/ryanwtf7/ryanlink */
10
+
11
+ var packageInfo = null;
12
+ function loadPackageInfo() {
13
+ if (packageInfo) {
14
+ return packageInfo;
15
+ }
16
+ try {
17
+ const possiblePaths = [
18
+ join(__dirname, "..", "..", "package.json"),
19
+ join(__dirname, "..", "package.json"),
20
+ join(process.cwd(), "package.json")
21
+ ];
22
+ for (const path of possiblePaths) {
23
+ try {
24
+ const content = readFileSync(path, "utf-8");
25
+ packageInfo = JSON.parse(content);
26
+ return packageInfo;
27
+ } catch {
28
+ continue;
29
+ }
30
+ }
31
+ console.warn("Could not load package.json, using fallback values");
32
+ return {
33
+ name: "ryanlink",
34
+ version: "1.0.0",
35
+ repository: {
36
+ type: "git",
37
+ url: "https://github.com/ryanwtf7/ryanlink.git"
38
+ }
39
+ };
40
+ } catch (error) {
41
+ return {
42
+ name: "ryanlink",
43
+ version: "1.0.0",
44
+ repository: {
45
+ type: "git",
46
+ url: "https://github.com/ryanwtf7/ryanlink.git"
47
+ }
48
+ };
49
+ }
50
+ }
51
+ var pkg = loadPackageInfo();
52
+ var CLIENT_NAME = pkg.name;
53
+ var CLIENT_VERSION = pkg.version;
54
+ var CLIENT_REPOSITORY = pkg.repository?.url.replace(/\.git$/, "") ?? "https://github.com/ryanwtf7/ryanlink";
55
+ var PACKAGE_INFO = Object.freeze({
56
+ name: CLIENT_NAME,
57
+ version: CLIENT_VERSION,
58
+ repository: CLIENT_REPOSITORY,
59
+ description: pkg.description
60
+ });
61
+
62
+ // src/config/defaults.ts
63
+ var DefaultRestOptions = Object.seal({
64
+ /**
65
+ * Lavalink API version
66
+ */
67
+ version: 4,
68
+ /**
69
+ * User agent string sent with requests
70
+ */
71
+ userAgent: `${CLIENT_NAME}/${CLIENT_VERSION} (${CLIENT_REPOSITORY})`,
72
+ /**
73
+ * Whether to include stack traces in error responses
74
+ */
75
+ stackTrace: false,
76
+ /**
77
+ * Request timeout in milliseconds
78
+ */
79
+ requestTimeout: 1e4
80
+ });
81
+ var DefaultNodeOptions = Object.seal({
82
+ /**
83
+ * Interval for stats updates in milliseconds
84
+ */
85
+ statsInterval: 6e4,
86
+ /**
87
+ * Highest acceptable latency in milliseconds
88
+ */
89
+ highestLatency: 2e3,
90
+ /**
91
+ * Delay before reconnection attempts in milliseconds
92
+ */
93
+ reconnectDelay: 1e4,
94
+ /**
95
+ * Maximum number of reconnection attempts (-1 = infinite)
96
+ */
97
+ reconnectLimit: 3,
98
+ /**
99
+ * WebSocket handshake timeout in milliseconds
100
+ */
101
+ handshakeTimeout: 5e3
102
+ });
103
+ var DefaultPlayerOptions = Object.seal({
104
+ /**
105
+ * Automatically initialize player on construction
106
+ */
107
+ autoInit: true,
108
+ /**
109
+ * Automatically sync queue state with Lavalink
110
+ */
111
+ autoSync: true,
112
+ /**
113
+ * Default search prefix (ytsearch, ytmsearch, scsearch, etc.)
114
+ */
115
+ queryPrefix: "ytsearch",
116
+ /**
117
+ * Automatically relocate queues when nodes disconnect
118
+ */
119
+ relocateQueues: true,
120
+ /**
121
+ * Default function to fetch related tracks for autoplay
122
+ * Returns empty array by default (no autoplay)
123
+ */
124
+ async fetchRelatedTracks() {
125
+ return await Promise.resolve([]);
126
+ }
127
+ });
128
+ var DefaultQueueOptions = Object.seal({
129
+ /**
130
+ * Default volume (0-1000)
131
+ */
132
+ volume: 100,
133
+ /**
134
+ * Default repeat mode
135
+ */
136
+ repeatMode: "none",
137
+ /**
138
+ * Default autoplay state
139
+ */
140
+ autoplay: false,
141
+ /**
142
+ * Default paused state
143
+ */
144
+ paused: false
145
+ });
146
+ var DefaultFilterOptions = Object.seal({
147
+ /**
148
+ * Volume multiplier (0.0 - 5.0)
149
+ */
150
+ volume: 1,
151
+ /**
152
+ * Equalizer bands (15 bands, -0.25 to 1.0 each)
153
+ */
154
+ equalizer: [],
155
+ /**
156
+ * Karaoke filter
157
+ */
158
+ karaoke: null,
159
+ /**
160
+ * Timescale filter (speed, pitch, rate)
161
+ */
162
+ timescale: null,
163
+ /**
164
+ * Tremolo filter (frequency, depth)
165
+ */
166
+ tremolo: null,
167
+ /**
168
+ * Vibrato filter (frequency, depth)
169
+ */
170
+ vibrato: null,
171
+ /**
172
+ * Rotation filter (rotationHz)
173
+ */
174
+ rotation: null,
175
+ /**
176
+ * Distortion filter
177
+ */
178
+ distortion: null,
179
+ /**
180
+ * Channel mix filter
181
+ */
182
+ channelMix: null,
183
+ /**
184
+ * Low pass filter
185
+ */
186
+ lowPass: null
187
+ });
188
+ var HttpStatusCodes = Object.freeze({
189
+ OK: 200,
190
+ CREATED: 201,
191
+ NO_CONTENT: 204,
192
+ BAD_REQUEST: 400,
193
+ UNAUTHORIZED: 401,
194
+ FORBIDDEN: 403,
195
+ NOT_FOUND: 404,
196
+ METHOD_NOT_ALLOWED: 405,
197
+ TOO_MANY_REQUESTS: 429,
198
+ INTERNAL_SERVER_ERROR: 500,
199
+ BAD_GATEWAY: 502,
200
+ SERVICE_UNAVAILABLE: 503,
201
+ GATEWAY_TIMEOUT: 504
202
+ });
203
+ var WebSocketCloseCodes = Object.freeze({
204
+ NORMAL: 1e3,
205
+ GOING_AWAY: 1001,
206
+ PROTOCOL_ERROR: 1002,
207
+ UNSUPPORTED_DATA: 1003,
208
+ NO_STATUS_RECEIVED: 1005,
209
+ ABNORMAL_CLOSURE: 1006,
210
+ INVALID_FRAME_PAYLOAD_DATA: 1007,
211
+ POLICY_VIOLATION: 1008,
212
+ MESSAGE_TOO_BIG: 1009,
213
+ INTERNAL_ERROR: 1011
214
+ });
215
+
216
+ // src/config/presets.ts
217
+ var EQPresets = {
218
+ /** A Bassboost Equalizer, so high it distorts the audio */
219
+ BassboostEarrape: [
220
+ { band: 0, gain: 0.6 * 0.375 },
221
+ { band: 1, gain: 0.67 * 0.375 },
222
+ { band: 2, gain: 0.67 * 0.375 },
223
+ { band: 3, gain: 0.4 * 0.375 },
224
+ { band: 4, gain: -0.5 * 0.375 },
225
+ { band: 5, gain: 0.15 * 0.375 },
226
+ { band: 6, gain: -0.45 * 0.375 },
227
+ { band: 7, gain: 0.23 * 0.375 },
228
+ { band: 8, gain: 0.35 * 0.375 },
229
+ { band: 9, gain: 0.45 * 0.375 },
230
+ { band: 10, gain: 0.55 * 0.375 },
231
+ { band: 11, gain: -0.6 * 0.375 },
232
+ { band: 12, gain: 0.55 * 0.375 },
233
+ { band: 13, gain: -0.5 * 0.375 },
234
+ { band: 14, gain: -0.75 * 0.375 }
235
+ ],
236
+ /** A High and decent Bassboost Equalizer */
237
+ BassboostHigh: [
238
+ { band: 0, gain: 0.6 * 0.25 },
239
+ { band: 1, gain: 0.67 * 0.25 },
240
+ { band: 2, gain: 0.67 * 0.25 },
241
+ { band: 3, gain: 0.4 * 0.25 },
242
+ { band: 4, gain: -0.5 * 0.25 },
243
+ { band: 5, gain: 0.15 * 0.25 },
244
+ { band: 6, gain: -0.45 * 0.25 },
245
+ { band: 7, gain: 0.23 * 0.25 },
246
+ { band: 8, gain: 0.35 * 0.25 },
247
+ { band: 9, gain: 0.45 * 0.25 },
248
+ { band: 10, gain: 0.55 * 0.25 },
249
+ { band: 11, gain: -0.6 * 0.25 },
250
+ { band: 12, gain: 0.55 * 0.25 },
251
+ { band: 13, gain: -0.5 * 0.25 },
252
+ { band: 14, gain: -0.75 * 0.25 }
253
+ ],
254
+ /** A decent Bassboost Equalizer */
255
+ BassboostMedium: [
256
+ { band: 0, gain: 0.6 * 0.1875 },
257
+ { band: 1, gain: 0.67 * 0.1875 },
258
+ { band: 2, gain: 0.67 * 0.1875 },
259
+ { band: 3, gain: 0.4 * 0.1875 },
260
+ { band: 4, gain: -0.5 * 0.1875 },
261
+ { band: 5, gain: 0.15 * 0.1875 },
262
+ { band: 6, gain: -0.45 * 0.1875 },
263
+ { band: 7, gain: 0.23 * 0.1875 },
264
+ { band: 8, gain: 0.35 * 0.1875 },
265
+ { band: 9, gain: 0.45 * 0.1875 },
266
+ { band: 10, gain: 0.55 * 0.1875 },
267
+ { band: 11, gain: -0.6 * 0.1875 },
268
+ { band: 12, gain: 0.55 * 0.1875 },
269
+ { band: 13, gain: -0.5 * 0.1875 },
270
+ { band: 14, gain: -0.75 * 0.1875 }
271
+ ],
272
+ /** A slight Bassboost Equalizer */
273
+ BassboostLow: [
274
+ { band: 0, gain: 0.6 * 0.125 },
275
+ { band: 1, gain: 0.67 * 0.125 },
276
+ { band: 2, gain: 0.67 * 0.125 },
277
+ { band: 3, gain: 0.4 * 0.125 },
278
+ { band: 4, gain: -0.5 * 0.125 },
279
+ { band: 5, gain: 0.15 * 0.125 },
280
+ { band: 6, gain: -0.45 * 0.125 },
281
+ { band: 7, gain: 0.23 * 0.125 },
282
+ { band: 8, gain: 0.35 * 0.125 },
283
+ { band: 9, gain: 0.45 * 0.125 },
284
+ { band: 10, gain: 0.55 * 0.125 },
285
+ { band: 11, gain: -0.6 * 0.125 },
286
+ { band: 12, gain: 0.55 * 0.125 },
287
+ { band: 13, gain: -0.5 * 0.125 },
288
+ { band: 14, gain: -0.75 * 0.125 }
289
+ ],
290
+ /** Professional high-fidelity audio preset for clear vocals and deep bass */
291
+ HighQuality: [
292
+ { band: 0, gain: 0.15 },
293
+ { band: 1, gain: 0.1 },
294
+ { band: 2, gain: 0.05 },
295
+ { band: 3, gain: 0 },
296
+ { band: 4, gain: 0 },
297
+ { band: 5, gain: 0 },
298
+ { band: 6, gain: 0 },
299
+ { band: 7, gain: 0 },
300
+ { band: 8, gain: 0 },
301
+ { band: 9, gain: 0 },
302
+ { band: 10, gain: 0.05 },
303
+ { band: 11, gain: 0.1 },
304
+ { band: 12, gain: 0.15 },
305
+ { band: 13, gain: 0.15 },
306
+ { band: 14, gain: 0.15 }
307
+ ],
308
+ /** Makes the Music slightly "better" */
309
+ BetterMusic: [
310
+ { band: 0, gain: 0.25 },
311
+ { band: 1, gain: 0.025 },
312
+ { band: 2, gain: 0.0125 },
313
+ { band: 3, gain: 0 },
314
+ { band: 4, gain: 0 },
315
+ { band: 5, gain: -0.0125 },
316
+ { band: 6, gain: -0.025 },
317
+ { band: 7, gain: -0.0175 },
318
+ { band: 8, gain: 0 },
319
+ { band: 9, gain: 0 },
320
+ { band: 10, gain: 0.0125 },
321
+ { band: 11, gain: 0.025 },
322
+ { band: 12, gain: 0.25 },
323
+ { band: 13, gain: 0.125 },
324
+ { band: 14, gain: 0.125 }
325
+ ],
326
+ /** Makes the Music sound like rock music / sound rock music better */
327
+ Rock: [
328
+ { band: 0, gain: 0.3 },
329
+ { band: 1, gain: 0.25 },
330
+ { band: 2, gain: 0.2 },
331
+ { band: 3, gain: 0.1 },
332
+ { band: 4, gain: 0.05 },
333
+ { band: 5, gain: -0.05 },
334
+ { band: 6, gain: -0.15 },
335
+ { band: 7, gain: -0.2 },
336
+ { band: 8, gain: -0.1 },
337
+ { band: 9, gain: -0.05 },
338
+ { band: 10, gain: 0.05 },
339
+ { band: 11, gain: 0.1 },
340
+ { band: 12, gain: 0.2 },
341
+ { band: 13, gain: 0.25 },
342
+ { band: 14, gain: 0.3 }
343
+ ],
344
+ /** Makes the Music sound like Classic music / sound Classic music better */
345
+ Classic: [
346
+ { band: 0, gain: 0.375 },
347
+ { band: 1, gain: 0.35 },
348
+ { band: 2, gain: 0.125 },
349
+ { band: 3, gain: 0 },
350
+ { band: 4, gain: 0 },
351
+ { band: 5, gain: 0.125 },
352
+ { band: 6, gain: 0.55 },
353
+ { band: 7, gain: 0.05 },
354
+ { band: 8, gain: 0.125 },
355
+ { band: 9, gain: 0.25 },
356
+ { band: 10, gain: 0.2 },
357
+ { band: 11, gain: 0.25 },
358
+ { band: 12, gain: 0.3 },
359
+ { band: 13, gain: 0.25 },
360
+ { band: 14, gain: 0.3 }
361
+ ],
362
+ /** Makes the Music sound like Pop music / sound Pop music better */
363
+ Pop: [
364
+ { band: 0, gain: 0.26 },
365
+ { band: 1, gain: 0.22 },
366
+ { band: 2, gain: 0.18 },
367
+ { band: 3, gain: 0.12 },
368
+ { band: 4, gain: 0.1 },
369
+ { band: 5, gain: 0.03 },
370
+ { band: 6, gain: -5e-3 },
371
+ { band: 7, gain: -0.01 },
372
+ { band: 8, gain: -0.015 },
373
+ { band: 9, gain: -0.015 },
374
+ { band: 10, gain: -0.01 },
375
+ { band: 11, gain: -5e-3 },
376
+ { band: 12, gain: 0.08 },
377
+ { band: 13, gain: 0.15 },
378
+ { band: 14, gain: 0.2 }
379
+ ],
380
+ /** Makes the Music sound like Electronic music / sound Electronic music better */
381
+ Electronic: [
382
+ { band: 0, gain: 0.375 },
383
+ { band: 1, gain: 0.35 },
384
+ { band: 2, gain: 0.125 },
385
+ { band: 3, gain: 0 },
386
+ { band: 4, gain: 0 },
387
+ { band: 5, gain: -0.125 },
388
+ { band: 6, gain: -0.125 },
389
+ { band: 7, gain: 0 },
390
+ { band: 8, gain: 0.25 },
391
+ { band: 9, gain: 0.125 },
392
+ { band: 10, gain: 0.15 },
393
+ { band: 11, gain: 0.2 },
394
+ { band: 12, gain: 0.25 },
395
+ { band: 13, gain: 0.35 },
396
+ { band: 14, gain: 0.4 }
397
+ ],
398
+ /** Boosts all Bands slightly for louder and fuller sound */
399
+ FullSound: [
400
+ { band: 0, gain: 0.25 },
401
+ { band: 1, gain: 0.25 },
402
+ { band: 2, gain: 0.25 },
403
+ { band: 3, gain: 0.25 },
404
+ { band: 4, gain: 0.25 },
405
+ { band: 5, gain: 0.25 },
406
+ { band: 6, gain: 0.25 },
407
+ { band: 7, gain: 0.25 },
408
+ { band: 8, gain: 0.25 },
409
+ { band: 9, gain: 0.25 },
410
+ { band: 10, gain: 0.25 },
411
+ { band: 11, gain: 0.25 },
412
+ { band: 12, gain: 0.25 },
413
+ { band: 13, gain: 0.25 },
414
+ { band: 14, gain: 0.25 }
415
+ ],
416
+ /** Makes the Music sound like being in a Gaming environment / sound Gaming music better */
417
+ Gaming: [
418
+ { band: 0, gain: 0.35 },
419
+ { band: 1, gain: 0.3 },
420
+ { band: 2, gain: 0.25 },
421
+ { band: 3, gain: 0.2 },
422
+ { band: 4, gain: 0.15 },
423
+ { band: 5, gain: 0.1 },
424
+ { band: 6, gain: 0.075 },
425
+ { band: 7, gain: 0 },
426
+ { band: 8, gain: -0.05 },
427
+ { band: 9, gain: 0.05 },
428
+ { band: 10, gain: 0.1 },
429
+ { band: 11, gain: 0.15 },
430
+ { band: 12, gain: 0.25 },
431
+ { band: 13, gain: 0.3 },
432
+ { band: 14, gain: 0.35 }
433
+ ],
434
+ /** Nightcore preset with speed and pitch adjustments */
435
+ Nightcore: [
436
+ { band: 0, gain: 0.2 },
437
+ { band: 1, gain: 0.15 },
438
+ { band: 2, gain: 0.1 },
439
+ { band: 3, gain: 0.05 },
440
+ { band: 4, gain: 0 },
441
+ { band: 5, gain: -0.05 },
442
+ { band: 6, gain: -0.1 },
443
+ { band: 7, gain: -0.05 },
444
+ { band: 8, gain: 0 },
445
+ { band: 9, gain: 0.05 },
446
+ { band: 10, gain: 0.1 },
447
+ { band: 11, gain: 0.15 },
448
+ { band: 12, gain: 0.2 },
449
+ { band: 13, gain: 0.25 },
450
+ { band: 14, gain: 0.3 }
451
+ ],
452
+ /** Vaporwave preset */
453
+ Vaporwave: [
454
+ { band: 0, gain: 0.35 },
455
+ { band: 1, gain: 0.3 },
456
+ { band: 2, gain: 0.2 },
457
+ { band: 3, gain: 0.1 },
458
+ { band: 4, gain: 0 },
459
+ { band: 5, gain: -0.05 },
460
+ { band: 6, gain: -0.1 },
461
+ { band: 7, gain: -0.15 },
462
+ { band: 8, gain: -0.1 },
463
+ { band: 9, gain: 0 },
464
+ { band: 10, gain: 0.1 },
465
+ { band: 11, gain: 0.15 },
466
+ { band: 12, gain: 0.2 },
467
+ { band: 13, gain: 0.25 },
468
+ { band: 14, gain: 0.3 }
469
+ ],
470
+ /** Treble and Bass boost */
471
+ TrebleBass: [
472
+ { band: 0, gain: 0.3 },
473
+ { band: 1, gain: 0.25 },
474
+ { band: 2, gain: 0.2 },
475
+ { band: 3, gain: 0.1 },
476
+ { band: 4, gain: 0 },
477
+ { band: 5, gain: -0.05 },
478
+ { band: 6, gain: -0.1 },
479
+ { band: 7, gain: -0.05 },
480
+ { band: 8, gain: 0 },
481
+ { band: 9, gain: 0.05 },
482
+ { band: 10, gain: 0.1 },
483
+ { band: 11, gain: 0.15 },
484
+ { band: 12, gain: 0.2 },
485
+ { band: 13, gain: 0.25 },
486
+ { band: 14, gain: 0.3 }
487
+ ],
488
+ /** Soft preset */
489
+ Soft: [
490
+ { band: 0, gain: 0.1 },
491
+ { band: 1, gain: 0.08 },
492
+ { band: 2, gain: 0.06 },
493
+ { band: 3, gain: 0.04 },
494
+ { band: 4, gain: 0.02 },
495
+ { band: 5, gain: 0 },
496
+ { band: 6, gain: 0 },
497
+ { band: 7, gain: 0 },
498
+ { band: 8, gain: 0 },
499
+ { band: 9, gain: 0 },
500
+ { band: 10, gain: 0.02 },
501
+ { band: 11, gain: 0.04 },
502
+ { band: 12, gain: 0.06 },
503
+ { band: 13, gain: 0.08 },
504
+ { band: 14, gain: 0.1 }
505
+ ],
506
+ /** TV preset */
507
+ TV: [
508
+ { band: 0, gain: 0.2 },
509
+ { band: 1, gain: 0.25 },
510
+ { band: 2, gain: 0.3 },
511
+ { band: 3, gain: 0.25 },
512
+ { band: 4, gain: 0.2 },
513
+ { band: 5, gain: 0.1 },
514
+ { band: 6, gain: 0 },
515
+ { band: 7, gain: 0 },
516
+ { band: 8, gain: 0.1 },
517
+ { band: 9, gain: 0.15 },
518
+ { band: 10, gain: 0.2 },
519
+ { band: 11, gain: 0.15 },
520
+ { band: 12, gain: 0.1 },
521
+ { band: 13, gain: 0.05 },
522
+ { band: 14, gain: 0 }
523
+ ],
524
+ /** Radio preset */
525
+ Radio: [
526
+ { band: 0, gain: 0.15 },
527
+ { band: 1, gain: 0.2 },
528
+ { band: 2, gain: 0.25 },
529
+ { band: 3, gain: 0.2 },
530
+ { band: 4, gain: 0.15 },
531
+ { band: 5, gain: 0.1 },
532
+ { band: 6, gain: 0 },
533
+ { band: 7, gain: 0 },
534
+ { band: 8, gain: 0 },
535
+ { band: 9, gain: 0.1 },
536
+ { band: 10, gain: 0.15 },
537
+ { band: 11, gain: 0.2 },
538
+ { band: 12, gain: 0.15 },
539
+ { band: 13, gain: 0.1 },
540
+ { band: 14, gain: 0.05 }
541
+ ],
542
+ /** Normalization preset (all bands at 0) */
543
+ Normalization: [
544
+ { band: 0, gain: 0 },
545
+ { band: 1, gain: 0 },
546
+ { band: 2, gain: 0 },
547
+ { band: 3, gain: 0 },
548
+ { band: 4, gain: 0 },
549
+ { band: 5, gain: 0 },
550
+ { band: 6, gain: 0 },
551
+ { band: 7, gain: 0 },
552
+ { band: 8, gain: 0 },
553
+ { band: 9, gain: 0 },
554
+ { band: 10, gain: 0 },
555
+ { band: 11, gain: 0 },
556
+ { band: 12, gain: 0 },
557
+ { band: 13, gain: 0 },
558
+ { band: 14, gain: 0 }
559
+ ]
560
+ };
561
+ function getEQPreset(name) {
562
+ return EQPresets[name];
563
+ }
564
+ function getEQPresetNames() {
565
+ return Object.keys(EQPresets);
566
+ }
567
+ function isValidEQPreset(name) {
568
+ return name in EQPresets;
569
+ }
570
+
571
+ // src/config/patterns.ts
572
+ var SnowflakeRegex = /^\d{17,20}$/;
573
+ var VoiceRegionIdRegex = /^([-a-z]{2,20})(?=[-a-z\d]*\.discord\.media:\d+$)/;
574
+ var UrlRegex = /^https?:\/\/.+/i;
575
+ var YoutubeRegex = /^(https?:\/\/)?(www\.)?(youtube\.com|youtu\.be)\/.+/i;
576
+ var SpotifyRegex = /^https?:\/\/open\.spotify\.com\/(track|album|playlist|artist)\/.+/i;
577
+ var SoundCloudRegex = /^https?:\/\/(www\.)?soundcloud\.com\/.+/i;
578
+ var BandcampRegex = /^https?:\/\/.+\.bandcamp\.com\/(track|album)\/.+/i;
579
+ var TwitchRegex = /^https?:\/\/(www\.)?twitch\.tv\/.+/i;
580
+ var AppleMusicRegex = /^https?:\/\/music\.apple\.com\/.+/i;
581
+ var DeezerRegex = /^https?:\/\/(www\.)?deezer\.com\/(track|album|playlist)\/.+/i;
582
+ var AudioFileRegex = /^https?:\/\/.+\.(mp3|wav|ogg|flac|m4a|aac|opus|webm)(\?.*)?$/i;
583
+ var TidalRegex = /^https?:\/\/(?:(?:listen|www)\.)?tidal\.com\/(?:browse\/)?(?:album|track|playlist|mix)\/[a-zA-Z0-9-]+(?:\/.*)?(?:\?.*)?$/i;
584
+ var YandexMusicRegex = /^(?:https?:\/\/)?music\.yandex\.(?:ru|com|kz|by)\/(?:artist|album|track|users\/[0-9A-Za-z@.-]+\/playlists|playlists)\/[0-9A-Za-z-.]+(?:\/track\/[0-9]+)?(?:\/)?$/i;
585
+ var AmazonMusicRegex = /^https?:\/\/music\.amazon\.[^/]+\/(?:albums|tracks|artists|playlists|user-playlists|community-playlists)\/[A-Za-z0-9]+(?:\/[^/?#]+)?(?:[/?].*)?$/i;
586
+ var JioSaavnRegex = /^(?:https?:\/\/)?(?:www\.)?jiosaavn\.com\/(?:song|album|featured|artist|s\/playlist)\/[a-zA-Z0-9_-]+(?:\/[a-zA-Z0-9_-]+)?$/i;
587
+ var PandoraRegex = /^@?(?:https?:\/\/)?(?:www\.)?pandora\.com\/(?:playlist\/PL:[\d:]+|artist\/[\w-]+(?:\/[\w-]+)*\/(?:TR|AL|AR)[A-Za-z0-9]+)(?:[?#].*)?$/i;
588
+ var QobuzRegex = /^https?:\/\/(?:www\.|play\.|open\.)?qobuz\.com\/(?:(?:[a-z]{2}-[a-z]{2}\/)?(?:album|playlist|track|artist)\/(?:.+?\/)?[a-zA-Z0-9]+|playlist\/\d+)$/i;
589
+ var AudiomackRegex = /^https?:\/\/audiomack\.com\/(?:(?:[^/]+\/(?:song|album|playlist)|(?:song|album|playlist))\/[^/?#]+)$/i;
590
+ var MixcloudRegex = /^https?:\/\/(?:(?:www|beta|m)\.)?mixcloud\.com\/[^/]+\/(?:playlists\/[^/]+|(?!stream|uploads|favorites|listens|playlists)[^/]+|(?:uploads|favorites|listens|stream))(?:\/)?$/i;
591
+ var AnghamiRegex = /^https?:\/\/(?:play\.)?anghami\.com\/(?:song|album|playlist|artist)\/[0-9]+$/i;
592
+ var AudiusRegex = /^https?:\/\/(?:www\.)?audius\.co\/[^/]+\/[^/]+$/i;
593
+ var GaanaRegex = /^https?:\/\/(?:www\.)?gaana\.com\/(?:song|album|playlist)\/[^/]+$/i;
594
+ var InstagramRegex = /^https?:\/\/(?:www\.)?instagram\.com\/(?:p|reel|tv)\/[A-Za-z0-9_-]+(?:\/)?$/i;
595
+ var ShazamRegex = /^https?:\/\/(?:www\.)?shazam\.com\/track\/[0-9]+$/i;
596
+
597
+ // src/config/endpoints.ts
598
+ var Routes = {
599
+ /**
600
+ * WebSocket endpoint
601
+ * @returns `/websocket`
602
+ */
603
+ websocket() {
604
+ return "/websocket";
605
+ },
606
+ /**
607
+ * Track loading endpoint
608
+ * @returns `/loadtracks`
609
+ */
610
+ trackLoading() {
611
+ return "/loadtracks";
612
+ },
613
+ /**
614
+ * Track decoding endpoint
615
+ * @param multiple Whether to decode multiple tracks
616
+ * @returns `/decodetrack` or `/decodetracks`
617
+ */
618
+ trackDecoding(multiple) {
619
+ if (multiple) {
620
+ return "/decodetracks";
621
+ }
622
+ return "/decodetrack";
623
+ },
624
+ /**
625
+ * Player endpoint
626
+ * @param sessionId Lavalink session ID
627
+ * @param guildId Optional guild ID for specific player
628
+ * @returns `/sessions/{sessionId}/players` or `/sessions/{sessionId}/players/{guildId}`
629
+ */
630
+ player(sessionId, guildId) {
631
+ if (guildId) {
632
+ return `/sessions/${sessionId}/players/${guildId}`;
633
+ }
634
+ return `/sessions/${sessionId}/players`;
635
+ },
636
+ /**
637
+ * Session endpoint
638
+ * @param sessionId Lavalink session ID
639
+ * @returns `/sessions/{sessionId}`
640
+ */
641
+ session(sessionId) {
642
+ return `/sessions/${sessionId}`;
643
+ },
644
+ /**
645
+ * Info endpoint
646
+ * @returns `/info`
647
+ */
648
+ info() {
649
+ return "/info";
650
+ },
651
+ /**
652
+ * Stats endpoint
653
+ * @returns `/stats`
654
+ */
655
+ stats() {
656
+ return "/stats";
657
+ },
658
+ /**
659
+ * Route planner endpoint
660
+ * @param free Optional action: `address` or `all`
661
+ * @returns `/routeplanner/status` or `/routeplanner/free/{action}`
662
+ */
663
+ routePlanner(free) {
664
+ if (free) {
665
+ return `/routeplanner/free/${free}`;
666
+ }
667
+ return "/routeplanner/status";
668
+ },
669
+ /**
670
+ * Version endpoint
671
+ * @returns `/version`
672
+ */
673
+ version() {
674
+ return "/version";
675
+ }
676
+ };
677
+
678
+ // src/config/symbols.ts
679
+ var LookupSymbol = /* @__PURE__ */ Symbol.for("lookup");
680
+ var UpdateSymbol = /* @__PURE__ */ Symbol.for("update");
681
+ var OnPingUpdateSymbol = /* @__PURE__ */ Symbol("onPingUpdate");
682
+ var OnVoiceCloseSymbol = /* @__PURE__ */ Symbol("onVoiceClose");
683
+ var OnStateUpdateSymbol = /* @__PURE__ */ Symbol("onStateUpdate");
684
+ var OnEventUpdateSymbol = /* @__PURE__ */ Symbol("onEventUpdate");
685
+
686
+ // src/utils/helpers.ts
687
+ var noop = () => {
688
+ };
689
+ var formatDuration = (ms) => {
690
+ if (!Number.isSafeInteger(ms) || ms <= 0) {
691
+ return "00:00";
692
+ }
693
+ const s = Math.floor(ms / 1e3);
694
+ const ss = `${s % 60}`.padStart(2, "0");
695
+ const mm = `${Math.floor(s / 60) % 60}`.padStart(2, "0");
696
+ if (s < 3600) {
697
+ return `${(s === 3600 ? "01:" : "") + mm}:${ss}`;
698
+ }
699
+ return `${`${Math.floor(s / 3600)}`.padStart(2, "0")}:${mm}:${ss}`;
700
+ };
701
+ var parseDuration = (duration) => {
702
+ const parts = duration.split(":").map(Number);
703
+ if (parts.length === 1) {
704
+ return (parts[0] ?? 0) * 1e3;
705
+ }
706
+ if (parts.length === 2) {
707
+ return ((parts[0] ?? 0) * 60 + (parts[1] ?? 0)) * 1e3;
708
+ }
709
+ if (parts.length === 3) {
710
+ return ((parts[0] ?? 0) * 3600 + (parts[1] ?? 0) * 60 + (parts[2] ?? 0)) * 1e3;
711
+ }
712
+ return 0;
713
+ };
714
+ var clamp = (value, min, max) => {
715
+ return Math.min(Math.max(value, min), max);
716
+ };
717
+ var sleep = (ms) => {
718
+ return new Promise((resolve) => setTimeout(resolve, ms));
719
+ };
720
+ var retry = async (fn, maxAttempts = 3, baseDelay = 1e3) => {
721
+ let lastError;
722
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
723
+ try {
724
+ return await fn();
725
+ } catch (error) {
726
+ lastError = error instanceof Error ? error : new Error(String(error));
727
+ if (attempt < maxAttempts - 1) {
728
+ const delay = baseDelay * Math.pow(2, attempt);
729
+ await sleep(delay);
730
+ }
731
+ }
732
+ }
733
+ throw lastError ?? new Error("Retry failed");
734
+ };
735
+ var shuffle = (array) => {
736
+ for (let i = array.length - 1; i > 0; i--) {
737
+ const j = Math.floor(Math.random() * (i + 1));
738
+ [array[i], array[j]] = [array[j], array[i]];
739
+ }
740
+ return array;
741
+ };
742
+ var randomElement = (array) => {
743
+ if (array.length === 0) {
744
+ return void 0;
745
+ }
746
+ return array[Math.floor(Math.random() * array.length)];
747
+ };
748
+ var chunk = (array, size) => {
749
+ const chunks = [];
750
+ for (let i = 0; i < array.length; i += size) {
751
+ chunks.push(array.slice(i, i + size));
752
+ }
753
+ return chunks;
754
+ };
755
+ var unique = (array, key) => {
756
+ if (!key) {
757
+ return [...new Set(array)];
758
+ }
759
+ const seen = /* @__PURE__ */ new Set();
760
+ return array.filter((item) => {
761
+ const k = key(item);
762
+ if (seen.has(k)) {
763
+ return false;
764
+ }
765
+ seen.add(k);
766
+ return true;
767
+ });
768
+ };
769
+ var isNumber = (input, check) => {
770
+ if (check === void 0) {
771
+ return Number.isFinite(input);
772
+ }
773
+ if (check === "integer" || check === "safe-int") {
774
+ return Number.isSafeInteger(input);
775
+ }
776
+ if (check === "natural" || check === "positive") {
777
+ return Number.isSafeInteger(input) && input > 0;
778
+ }
779
+ if (check === "whole") {
780
+ return Number.isSafeInteger(input) && input >= 0;
781
+ }
782
+ return false;
783
+ };
784
+ var isString = (input, check) => {
785
+ if (typeof input !== "string") {
786
+ return false;
787
+ }
788
+ if (check === void 0) {
789
+ return true;
790
+ }
791
+ if (check === "url") {
792
+ return URL.canParse(input);
793
+ }
794
+ if (check === "non-empty") {
795
+ return input.trim().length > 0;
796
+ }
797
+ if (check instanceof RegExp) {
798
+ return check.test(input);
799
+ }
800
+ return false;
801
+ };
802
+ var isRecord = (input, check) => {
803
+ if (!input || input.constructor !== Object) {
804
+ return false;
805
+ }
806
+ if (check === void 0) {
807
+ return true;
808
+ }
809
+ if (check === "non-empty") {
810
+ return Object.keys(input).length > 0;
811
+ }
812
+ return false;
813
+ };
814
+ var isArray = (input, check) => {
815
+ if (!Array.isArray(input)) {
816
+ return false;
817
+ }
818
+ if (check === void 0) {
819
+ return true;
820
+ }
821
+ if (check === "non-empty") {
822
+ return input.length !== 0;
823
+ }
824
+ if (typeof check === "function") {
825
+ return input.every(check);
826
+ }
827
+ return false;
828
+ };
829
+ var isBoolean = (input) => {
830
+ return typeof input === "boolean";
831
+ };
832
+ var isFunction = (input) => {
833
+ return typeof input === "function";
834
+ };
835
+ var isNullish = (input) => {
836
+ return input === null || input === void 0;
837
+ };
838
+ var isSnowflake = (input) => {
839
+ return isString(input) && /^\d{17,20}$/.test(input);
840
+ };
841
+ var isUrl = (input) => {
842
+ return isString(input, "url");
843
+ };
844
+ var isError = (error) => {
845
+ return error instanceof Error;
846
+ };
847
+ function assert(condition, message) {
848
+ if (!condition) {
849
+ throw new Error(message);
850
+ }
851
+ }
852
+ function validateNodeOptions(options) {
853
+ assert(isString(options.name, "non-empty"), "Node name must be a non-empty string");
854
+ assert(isString(options.host, "non-empty"), "Node host must be a non-empty string");
855
+ assert(isNumber(options.port, "natural"), "Node port must be a positive number");
856
+ assert(isString(options.password, "non-empty"), "Node password must be a non-empty string");
857
+ }
858
+ function validatePlayerOptions(options) {
859
+ assert(isArray(options.nodes, "non-empty"), "At least one node is required");
860
+ assert(isFunction(options.forwardVoiceUpdate), "forwardVoiceUpdate must be a function");
861
+ if (options.queryPrefix !== void 0) {
862
+ assert(isString(options.queryPrefix, "non-empty"), "queryPrefix must be a non-empty string");
863
+ }
864
+ }
865
+
866
+ // src/lavalink/HttpClient.ts
867
+ var REST = class {
868
+ #origin;
869
+ #headers;
870
+ #sessionId = null;
871
+ #requestTimeout;
872
+ userAgent;
873
+ constructor(options) {
874
+ if (options.origin) {
875
+ if (!options.origin.startsWith("http://") && !options.origin.startsWith("https://")) {
876
+ throw new Error("Origin must start with http:// or https://");
877
+ }
878
+ this.#origin = options.origin;
879
+ } else {
880
+ const protocol = options.secure ? "https" : "http";
881
+ const port = options.port ?? 2333;
882
+ const host = options.host ?? "localhost";
883
+ this.#origin = `${protocol}://${host}:${port}`;
884
+ }
885
+ if (options.version !== void 0 && options.version <= 0) {
886
+ throw new Error("Version must be a positive number");
887
+ }
888
+ if (options.password.includes("\n") || options.password.includes("\r")) {
889
+ throw new Error("Password cannot contain newline characters");
890
+ }
891
+ const userAgent = options.userAgent ?? `${CLIENT_NAME}/${CLIENT_VERSION} (${CLIENT_REPOSITORY})`;
892
+ if (userAgent.includes("\n") || userAgent.includes("\r")) {
893
+ throw new Error("User agent cannot contain newline characters");
894
+ }
895
+ this.userAgent = userAgent;
896
+ const requestTimeout = options.requestTimeout ?? 1e4;
897
+ if (requestTimeout <= 0) {
898
+ throw new Error("Request timeout must be a positive number");
899
+ }
900
+ this.#requestTimeout = requestTimeout;
901
+ this.#headers = {
902
+ Authorization: options.password,
903
+ "User-Agent": this.userAgent,
904
+ "Content-Type": "application/json"
905
+ };
906
+ if (options.sessionId) {
907
+ this.#sessionId = options.sessionId;
908
+ }
909
+ }
910
+ get origin() {
911
+ return this.#origin;
912
+ }
913
+ get sessionId() {
914
+ return this.#sessionId;
915
+ }
916
+ set sessionId(value) {
917
+ if (value !== null && (typeof value !== "string" || value.trim() === "")) {
918
+ return;
919
+ }
920
+ this.#sessionId = value;
921
+ }
922
+ /**
923
+ * Make a request to the Lavalink REST API
924
+ */
925
+ async #request(path, options = {}, timeout) {
926
+ const url = `${this.#origin}${path}`;
927
+ const controller = new AbortController();
928
+ const timeoutId = setTimeout(() => controller.abort(), timeout ?? this.#requestTimeout);
929
+ try {
930
+ const response = await fetch(url, {
931
+ ...options,
932
+ headers: {
933
+ ...this.#headers,
934
+ ...options.headers
935
+ },
936
+ signal: controller.signal
937
+ });
938
+ clearTimeout(timeoutId);
939
+ if (!response.ok) {
940
+ const error = await this.#parseError(response);
941
+ throw error;
942
+ }
943
+ if (response.status === 204) {
944
+ return void 0;
945
+ }
946
+ return await response.json();
947
+ } catch (error) {
948
+ clearTimeout(timeoutId);
949
+ if (error instanceof Error && error.name === "AbortError") {
950
+ throw new Error(`Request timeout after ${timeout ?? this.#requestTimeout}ms`);
951
+ }
952
+ throw error;
953
+ }
954
+ }
955
+ async #parseError(response) {
956
+ let message = `HTTP ${response.status}: ${response.statusText}`;
957
+ try {
958
+ const data = await response.json();
959
+ if (data.error) {
960
+ message = data.error;
961
+ } else if (data.message) {
962
+ message = data.message;
963
+ }
964
+ } catch {
965
+ }
966
+ const error = new Error(message);
967
+ error.name = "LavalinkRestError";
968
+ return error;
969
+ }
970
+ /**
971
+ * Load tracks from a query or URL
972
+ */
973
+ async loadTracks(identifier) {
974
+ const path = `${Routes.trackLoading()}?identifier=${encodeURIComponent(identifier)}`;
975
+ return this.#request(path, { method: "GET" });
976
+ }
977
+ /**
978
+ * Decode a single track
979
+ */
980
+ async decodeTrack(encoded) {
981
+ const path = `${Routes.trackDecoding()}?encodedTrack=${encodeURIComponent(encoded)}`;
982
+ return this.#request(path, { method: "GET" });
983
+ }
984
+ /**
985
+ * Decode multiple tracks
986
+ */
987
+ async decodeTracks(encoded) {
988
+ return this.#request(Routes.trackDecoding(true), {
989
+ method: "POST",
990
+ body: JSON.stringify(encoded)
991
+ });
992
+ }
993
+ /**
994
+ * Fetch all players for a session
995
+ */
996
+ async fetchPlayers() {
997
+ if (!this.#sessionId) {
998
+ throw new Error("No session ID available");
999
+ }
1000
+ return this.#request(Routes.player(this.#sessionId), { method: "GET" });
1001
+ }
1002
+ /**
1003
+ * Fetch a specific player
1004
+ */
1005
+ async fetchPlayer(guildId) {
1006
+ if (!this.#sessionId) {
1007
+ throw new Error("No session ID available");
1008
+ }
1009
+ return this.#request(Routes.player(this.#sessionId, guildId), { method: "GET" });
1010
+ }
1011
+ /**
1012
+ * Update a player
1013
+ */
1014
+ async updatePlayer(guildId, data, params) {
1015
+ if (!this.#sessionId) {
1016
+ throw new Error("No session ID available");
1017
+ }
1018
+ let path = Routes.player(this.#sessionId, guildId);
1019
+ if (params?.noReplace) {
1020
+ path += "?noReplace=true";
1021
+ }
1022
+ return this.#request(path, {
1023
+ method: "PATCH",
1024
+ body: JSON.stringify(data)
1025
+ });
1026
+ }
1027
+ /**
1028
+ * Destroy a player
1029
+ */
1030
+ async destroyPlayer(guildId) {
1031
+ if (!this.#sessionId) {
1032
+ throw new Error("No session ID available");
1033
+ }
1034
+ return this.#request(Routes.player(this.#sessionId, guildId), { method: "DELETE" });
1035
+ }
1036
+ /**
1037
+ * Update session configuration
1038
+ */
1039
+ async updateSession(data) {
1040
+ if (!this.#sessionId) {
1041
+ throw new Error("No session ID available");
1042
+ }
1043
+ return this.#request(Routes.session(this.#sessionId), {
1044
+ method: "PATCH",
1045
+ body: JSON.stringify(data)
1046
+ });
1047
+ }
1048
+ /**
1049
+ * Fetch Lavalink server info
1050
+ */
1051
+ async fetchInfo() {
1052
+ return this.#request(Routes.info(), { method: "GET" });
1053
+ }
1054
+ /**
1055
+ * Fetch node statistics
1056
+ */
1057
+ async fetchStats() {
1058
+ return this.#request(Routes.stats(), { method: "GET" });
1059
+ }
1060
+ /**
1061
+ * Fetch Lavalink version
1062
+ */
1063
+ async fetchVersion() {
1064
+ return this.#request("/version", { method: "GET" });
1065
+ }
1066
+ /**
1067
+ * Fetch route planner status
1068
+ */
1069
+ async fetchRoutePlannerStatus() {
1070
+ return this.#request(Routes.routePlanner(), { method: "GET" });
1071
+ }
1072
+ /**
1073
+ * Free a specific address from the route planner
1074
+ */
1075
+ async freeRoutePlannerAddress(address) {
1076
+ return this.#request(Routes.routePlanner("address"), {
1077
+ method: "POST",
1078
+ body: JSON.stringify({ address })
1079
+ });
1080
+ }
1081
+ /**
1082
+ * Free all addresses from the route planner
1083
+ */
1084
+ async freeAllRoutePlannerAddresses() {
1085
+ return this.#request(Routes.routePlanner("all"), { method: "POST" });
1086
+ }
1087
+ /**
1088
+ * Set SponsorBlock segments for a player
1089
+ */
1090
+ async setSponsorBlock(guildId, segments) {
1091
+ if (!this.#sessionId) {
1092
+ throw new Error("No session ID available");
1093
+ }
1094
+ return this.#request(`${Routes.player(this.#sessionId, guildId)}/sponsorblock`, {
1095
+ method: "PATCH",
1096
+ body: JSON.stringify(segments)
1097
+ });
1098
+ }
1099
+ /**
1100
+ * Get current SponsorBlock segments for a player
1101
+ */
1102
+ async getSponsorBlock(guildId) {
1103
+ if (!this.#sessionId) {
1104
+ throw new Error("No session ID available");
1105
+ }
1106
+ return this.#request(`${Routes.player(this.#sessionId, guildId)}/sponsorblock`, { method: "GET" });
1107
+ }
1108
+ /**
1109
+ * Delete SponsorBlock configuration for a player
1110
+ */
1111
+ async deleteSponsorBlock(guildId) {
1112
+ if (!this.#sessionId) {
1113
+ throw new Error("No session ID available");
1114
+ }
1115
+ return this.#request(`${Routes.player(this.#sessionId, guildId)}/sponsorblock`, { method: "DELETE" });
1116
+ }
1117
+ };
1118
+ var CloseCodes = /* @__PURE__ */ ((CloseCodes2) => {
1119
+ CloseCodes2[CloseCodes2["Normal"] = 1e3] = "Normal";
1120
+ CloseCodes2[CloseCodes2["GoingAway"] = 1001] = "GoingAway";
1121
+ CloseCodes2[CloseCodes2["ProtocolError"] = 1002] = "ProtocolError";
1122
+ CloseCodes2[CloseCodes2["UnsupportedData"] = 1003] = "UnsupportedData";
1123
+ CloseCodes2[CloseCodes2["NoStatusReceived"] = 1005] = "NoStatusReceived";
1124
+ CloseCodes2[CloseCodes2["AbnormalClosure"] = 1006] = "AbnormalClosure";
1125
+ CloseCodes2[CloseCodes2["InvalidFramePayloadData"] = 1007] = "InvalidFramePayloadData";
1126
+ CloseCodes2[CloseCodes2["PolicyViolation"] = 1008] = "PolicyViolation";
1127
+ CloseCodes2[CloseCodes2["MessageTooBig"] = 1009] = "MessageTooBig";
1128
+ CloseCodes2[CloseCodes2["InternalError"] = 1011] = "InternalError";
1129
+ return CloseCodes2;
1130
+ })(CloseCodes || {});
1131
+ var LavalinkNode = class extends EventEmitter {
1132
+ #socketConfig;
1133
+ #connectPromise = null;
1134
+ #disconnectPromise = null;
1135
+ #pingTimer = null;
1136
+ #reconnectTimer = null;
1137
+ #ping = null;
1138
+ #lastPingTime = null;
1139
+ #reconnectCycle = true;
1140
+ #reconnectAttempts = 0;
1141
+ #manualDisconnect = false;
1142
+ #socket = null;
1143
+ #stats = null;
1144
+ #socketUrl;
1145
+ #pingTimeout;
1146
+ #reconnectDelay;
1147
+ #reconnectLimit;
1148
+ name;
1149
+ rest;
1150
+ constructor(options) {
1151
+ super({ captureRejections: false });
1152
+ if (!options.name || typeof options.name !== "string") {
1153
+ throw new Error("Node name must be a non-empty string");
1154
+ }
1155
+ if (!options.clientId || typeof options.clientId !== "string") {
1156
+ throw new Error("Client ID must be a non-empty string");
1157
+ }
1158
+ this.rest = new REST(options);
1159
+ this.#socketConfig = {
1160
+ headers: {
1161
+ "Client-Name": `${CLIENT_NAME}/${CLIENT_VERSION}`,
1162
+ "User-Id": options.clientId,
1163
+ "User-Agent": this.rest.userAgent,
1164
+ Authorization: options.password
1165
+ },
1166
+ perMessageDeflate: false,
1167
+ handshakeTimeout: options.handshakeTimeout ?? 3e4
1168
+ };
1169
+ if (this.rest.sessionId) {
1170
+ this.#socketConfig.headers["Session-Id"] = this.rest.sessionId;
1171
+ }
1172
+ const protocol = options.secure ? "wss" : "ws";
1173
+ this.#socketUrl = `${protocol}://${options.host}:${options.port}/v4/websocket`;
1174
+ this.#pingTimeout = (options.statsInterval ?? 6e4) + (options.highestLatency ?? 5e3);
1175
+ this.#reconnectDelay = options.reconnectDelay ?? 5e3;
1176
+ this.#reconnectLimit = options.reconnectLimit ?? -1;
1177
+ this.name = options.name;
1178
+ Object.defineProperty(this.rest, "sessionId", {
1179
+ configurable: false,
1180
+ get: () => this.sessionId,
1181
+ set: () => {
1182
+ }
1183
+ });
1184
+ const immutable = {
1185
+ writable: false,
1186
+ configurable: false
1187
+ };
1188
+ Object.defineProperties(this, {
1189
+ name: immutable,
1190
+ rest: immutable
1191
+ });
1192
+ }
1193
+ get clientId() {
1194
+ return this.#socketConfig.headers["User-Id"];
1195
+ }
1196
+ get sessionId() {
1197
+ return this.#socketConfig.headers["Session-Id"] ?? null;
1198
+ }
1199
+ get ping() {
1200
+ return this.#ping;
1201
+ }
1202
+ get stats() {
1203
+ return this.#stats;
1204
+ }
1205
+ get state() {
1206
+ if (this.connecting) {
1207
+ return "connecting";
1208
+ }
1209
+ if (this.connected) {
1210
+ return this.ready ? "ready" : "connected";
1211
+ }
1212
+ return this.reconnecting ? "reconnecting" : "disconnected";
1213
+ }
1214
+ get connecting() {
1215
+ return this.#socket?.readyState === WebSocket.CONNECTING;
1216
+ }
1217
+ get connected() {
1218
+ return this.#socket?.readyState === WebSocket.OPEN;
1219
+ }
1220
+ get ready() {
1221
+ return this.connected && this.sessionId !== null;
1222
+ }
1223
+ get reconnecting() {
1224
+ return this.#socket === null && this.#reconnectTimer !== null;
1225
+ }
1226
+ get disconnected() {
1227
+ return this.#socket === null && !this.reconnecting;
1228
+ }
1229
+ get reconnectLimit() {
1230
+ return this.#reconnectLimit;
1231
+ }
1232
+ get reconnectAttempts() {
1233
+ return this.#reconnectAttempts;
1234
+ }
1235
+ #error(err) {
1236
+ const data = "errors" in err && Array.isArray(err.errors) ? err.errors[err.errors.length - 1] : err;
1237
+ const error = data instanceof Error ? data : new Error(String(data));
1238
+ error.name = `Error [${this.constructor.name}]`;
1239
+ return error;
1240
+ }
1241
+ #cleanup() {
1242
+ this.#socket?.removeAllListeners();
1243
+ if (this.#pingTimer !== null) {
1244
+ clearTimeout$1(this.#pingTimer);
1245
+ }
1246
+ this.#socket = this.#pingTimer = this.#stats = null;
1247
+ this.#lastPingTime = this.#ping = null;
1248
+ }
1249
+ #reconnect() {
1250
+ this.#reconnectCycle = false;
1251
+ this.#reconnectTimer?.refresh();
1252
+ this.#reconnectTimer ??= setTimeout$1(() => {
1253
+ this.#reconnectCycle = true;
1254
+ void this.connect();
1255
+ }, this.#reconnectDelay).unref();
1256
+ }
1257
+ #stopReconnecting(resetCount = true, reconnectCycle = false) {
1258
+ this.#reconnectCycle = reconnectCycle;
1259
+ if (resetCount) {
1260
+ this.#reconnectAttempts = 0;
1261
+ }
1262
+ if (this.#reconnectTimer !== null) {
1263
+ clearTimeout$1(this.#reconnectTimer);
1264
+ }
1265
+ this.#reconnectTimer = null;
1266
+ }
1267
+ #keepAliveAndPing() {
1268
+ this.#pingTimer?.refresh();
1269
+ this.#pingTimer ??= setTimeout$1(() => {
1270
+ this.#socket?.terminate();
1271
+ this.#cleanup();
1272
+ this.#reconnect();
1273
+ }, this.#pingTimeout).unref();
1274
+ this.#lastPingTime = Date.now();
1275
+ this.#socket?.ping();
1276
+ }
1277
+ #parseMessageData(data) {
1278
+ try {
1279
+ return JSON.parse(data);
1280
+ } catch {
1281
+ return null;
1282
+ }
1283
+ }
1284
+ /**
1285
+ * Connect to the Lavalink node
1286
+ * Handles reconnection attempts and session resumption
1287
+ */
1288
+ async connect() {
1289
+ if (this.#socket !== null) {
1290
+ return this.#connectPromise ?? this.connected;
1291
+ }
1292
+ if (this.reconnecting) {
1293
+ this.#reconnectAttempts++;
1294
+ if (!this.#reconnectCycle) {
1295
+ this.#stopReconnecting(false, true);
1296
+ }
1297
+ }
1298
+ this.#socket = new WebSocket(this.#socketUrl, this.#socketConfig);
1299
+ this.#socket.once("open", () => {
1300
+ this.emit("connect", this.#reconnectAttempts, this.name);
1301
+ });
1302
+ this.#socket.on("message", (data) => {
1303
+ void this.#onMessage(data.toString("utf8"));
1304
+ });
1305
+ this.#socket.on("error", (err) => {
1306
+ this.emit("error", this.#error(err), this.name);
1307
+ });
1308
+ this.#socket.on("close", (code, reason) => {
1309
+ this.#onClose(code, reason.toString("utf8"));
1310
+ });
1311
+ this.#socket.on("pong", () => {
1312
+ if (this.#lastPingTime === null) {
1313
+ return;
1314
+ }
1315
+ this.#ping = Math.max(0, Date.now() - this.#lastPingTime);
1316
+ });
1317
+ let resolve;
1318
+ let reject;
1319
+ const promise = new Promise((res, rej) => {
1320
+ resolve = res;
1321
+ reject = rej;
1322
+ });
1323
+ const resolver = { promise, resolve, reject };
1324
+ this.#connectPromise = resolver.promise;
1325
+ const controller = new AbortController();
1326
+ try {
1327
+ await Promise.race([
1328
+ once(this.#socket, "open", { signal: controller.signal }),
1329
+ once(this.#socket, "close", { signal: controller.signal })
1330
+ ]);
1331
+ } catch {
1332
+ this.#cleanup();
1333
+ } finally {
1334
+ controller.abort();
1335
+ const connected = this.connected;
1336
+ resolver.resolve(connected);
1337
+ this.#connectPromise = null;
1338
+ }
1339
+ return this.connected;
1340
+ }
1341
+ /**
1342
+ * Disconnect from the Lavalink node
1343
+ * @param code - WebSocket close code
1344
+ * @param reason - Disconnect reason
1345
+ */
1346
+ async disconnect(code = 1e3 /* Normal */, reason = "disconnected") {
1347
+ if (this.#disconnectPromise !== null) {
1348
+ return this.#disconnectPromise;
1349
+ }
1350
+ this.#stopReconnecting();
1351
+ if (this.#socket === null) {
1352
+ return;
1353
+ }
1354
+ if (this.connecting) {
1355
+ this.#manualDisconnect = true;
1356
+ this.#socket.terminate();
1357
+ return;
1358
+ }
1359
+ if (!this.connected) {
1360
+ return;
1361
+ }
1362
+ this.#manualDisconnect = true;
1363
+ this.#disconnectPromise = once(this.#socket, "close").then(
1364
+ () => {
1365
+ },
1366
+ () => {
1367
+ }
1368
+ );
1369
+ this.#socket.close(code, reason);
1370
+ await this.#disconnectPromise;
1371
+ this.#disconnectPromise = null;
1372
+ }
1373
+ async #onMessage(data) {
1374
+ const payload = this.#parseMessageData(data);
1375
+ if (payload === null) {
1376
+ return this.disconnect(1003 /* UnsupportedData */, "expected json payload");
1377
+ }
1378
+ if (payload.op === "stats" /* Stats */) {
1379
+ this.#stats = payload;
1380
+ this.#keepAliveAndPing();
1381
+ } else if (payload.op === "ready" /* Ready */) {
1382
+ this.#stopReconnecting();
1383
+ this.#socketConfig.headers["Session-Id"] = payload.sessionId;
1384
+ this.emit("ready", payload.resumed, payload.sessionId, this.name);
1385
+ }
1386
+ this.emit("dispatch", payload, this.name);
1387
+ }
1388
+ #onClose(code, reason) {
1389
+ this.#cleanup();
1390
+ const shouldStop = this.#manualDisconnect || this.#reconnectLimit >= 0 && this.#reconnectAttempts >= this.#reconnectLimit;
1391
+ if (shouldStop) {
1392
+ this.#stopReconnecting();
1393
+ delete this.#socketConfig.headers["Session-Id"];
1394
+ const byLocal = this.#manualDisconnect;
1395
+ this.#manualDisconnect = false;
1396
+ this.emit("disconnect", code, reason, byLocal, this.name);
1397
+ return;
1398
+ }
1399
+ if (this.#reconnectCycle) {
1400
+ this.#reconnect();
1401
+ this.emit("close", code, reason, this.name);
1402
+ return;
1403
+ }
1404
+ setTimeout$1(() => {
1405
+ this.#reconnectCycle = true;
1406
+ void this.connect();
1407
+ }, 0);
1408
+ }
1409
+ /**
1410
+ * Set SponsorBlock segments for a player
1411
+ */
1412
+ async setSponsorBlock(player, segments) {
1413
+ return this.rest.setSponsorBlock(player.guildId, segments);
1414
+ }
1415
+ /**
1416
+ * Get current SponsorBlock segments for a player
1417
+ */
1418
+ async getSponsorBlock(player) {
1419
+ return this.rest.getSponsorBlock(player.guildId);
1420
+ }
1421
+ /**
1422
+ * Delete SponsorBlock configuration for a player
1423
+ */
1424
+ async deleteSponsorBlock(player) {
1425
+ return this.rest.deleteSponsorBlock(player.guildId);
1426
+ }
1427
+ toString() {
1428
+ return this.name;
1429
+ }
1430
+ };
1431
+ var NodeManager = class extends EventEmitter {
1432
+ #player;
1433
+ #nodes = /* @__PURE__ */ new Map();
1434
+ info = /* @__PURE__ */ new Map();
1435
+ // LavalinkInfo cache
1436
+ constructor(player) {
1437
+ super({ captureRejections: false });
1438
+ this.#player = player;
1439
+ const immutable = {
1440
+ writable: false,
1441
+ configurable: false
1442
+ };
1443
+ Object.defineProperties(this, {
1444
+ info: immutable
1445
+ });
1446
+ }
1447
+ /**
1448
+ * Get a node by name
1449
+ */
1450
+ get(name) {
1451
+ return this.#nodes.get(name);
1452
+ }
1453
+ /**
1454
+ * Check if a node exists
1455
+ */
1456
+ has(name) {
1457
+ return this.#nodes.has(name);
1458
+ }
1459
+ /**
1460
+ * Get all nodes
1461
+ */
1462
+ get all() {
1463
+ return Array.from(this.#nodes.values());
1464
+ }
1465
+ /**
1466
+ * Get number of nodes
1467
+ */
1468
+ get size() {
1469
+ return this.#nodes.size;
1470
+ }
1471
+ /**
1472
+ * Create a new node
1473
+ */
1474
+ create(options) {
1475
+ if (this.#nodes.has(options.name)) {
1476
+ throw new Error(`Node '${options.name}' already exists`);
1477
+ }
1478
+ const node = new LavalinkNode({
1479
+ ...options,
1480
+ clientId: this.#player.clientId ?? ""
1481
+ });
1482
+ node.on("connect", (_attempts, _name) => {
1483
+ this.emit("connect", node);
1484
+ });
1485
+ node.on("ready", (resumed, sessionId, _name) => {
1486
+ this.emit("ready", node, resumed, sessionId);
1487
+ if (!this.info.has(node.name)) {
1488
+ this.fetchInfo(node.name).catch(() => {
1489
+ });
1490
+ }
1491
+ if (resumed) {
1492
+ void this.#handleResumed(node);
1493
+ }
1494
+ });
1495
+ node.on("disconnect", (code, reason, _byLocal, _name) => {
1496
+ this.emit("disconnect", node, { code, reason });
1497
+ });
1498
+ node.on("close", (_code, _reason, _name) => {
1499
+ this.emit("reconnecting", node);
1500
+ });
1501
+ node.on("error", (error, _name) => {
1502
+ this.emit("error", node, error);
1503
+ });
1504
+ node.on("dispatch", (payload, _name) => {
1505
+ this.emit("raw", node, payload);
1506
+ });
1507
+ this.#nodes.set(options.name, node);
1508
+ this.emit("create", node);
1509
+ return node;
1510
+ }
1511
+ /**
1512
+ * Delete a node
1513
+ */
1514
+ async delete(name) {
1515
+ const node = this.#nodes.get(name);
1516
+ if (!node) {
1517
+ return false;
1518
+ }
1519
+ const activeQueues = this.#player.queues.all.filter((q) => q.node?.name === name);
1520
+ if (activeQueues.length > 0) {
1521
+ throw new Error(`Cannot delete node '${name}' with ${activeQueues.length} active queue(s)`);
1522
+ }
1523
+ await node.disconnect();
1524
+ this.#nodes.delete(name);
1525
+ this.info.delete(name);
1526
+ this.emit("destroy", node);
1527
+ return true;
1528
+ }
1529
+ /**
1530
+ * Connect all nodes
1531
+ */
1532
+ async connect() {
1533
+ const promises = Array.from(this.#nodes.values()).map((node) => node.connect());
1534
+ await Promise.allSettled(promises);
1535
+ }
1536
+ /**
1537
+ * Disconnect all nodes
1538
+ */
1539
+ async disconnect() {
1540
+ const promises = Array.from(this.#nodes.values()).map((node) => node.disconnect());
1541
+ await Promise.allSettled(promises);
1542
+ }
1543
+ /**
1544
+ * Get relevant nodes sorted by load and availability
1545
+ * Returns nodes that are ready and have lowest load
1546
+ */
1547
+ relevant() {
1548
+ const readyNodes = Array.from(this.#nodes.values()).filter((node) => node.ready);
1549
+ if (readyNodes.length === 0) {
1550
+ return [];
1551
+ }
1552
+ return readyNodes.sort((a, b) => {
1553
+ const aStats = a.stats;
1554
+ const bStats = b.stats;
1555
+ if (!aStats) {
1556
+ return 1;
1557
+ }
1558
+ if (!bStats) {
1559
+ return -1;
1560
+ }
1561
+ const aLoad = aStats.playingPlayers / (aStats.players || 1);
1562
+ const bLoad = bStats.playingPlayers / (bStats.players || 1);
1563
+ if (aLoad !== bLoad) {
1564
+ return aLoad - bLoad;
1565
+ }
1566
+ const aCpu = aStats.cpu.lavalinkLoad;
1567
+ const bCpu = bStats.cpu.lavalinkLoad;
1568
+ if (aCpu !== bCpu) {
1569
+ return aCpu - bCpu;
1570
+ }
1571
+ const aMemory = aStats.memory.used / aStats.memory.allocated;
1572
+ const bMemory = bStats.memory.used / bStats.memory.allocated;
1573
+ return aMemory - bMemory;
1574
+ });
1575
+ }
1576
+ /**
1577
+ * Get node metrics
1578
+ */
1579
+ get metrics() {
1580
+ const nodes = Array.from(this.#nodes.values());
1581
+ return {
1582
+ total: nodes.length,
1583
+ connected: nodes.filter((n) => n.connected).length,
1584
+ ready: nodes.filter((n) => n.ready).length,
1585
+ reconnecting: nodes.filter((n) => n.reconnecting).length,
1586
+ players: nodes.reduce((sum, n) => sum + (n.stats?.players ?? 0), 0),
1587
+ playingPlayers: nodes.reduce((sum, n) => sum + (n.stats?.playingPlayers ?? 0), 0)
1588
+ };
1589
+ }
1590
+ /**
1591
+ * Get all node names
1592
+ */
1593
+ keys() {
1594
+ return this.#nodes.keys();
1595
+ }
1596
+ /**
1597
+ * Get all nodes
1598
+ */
1599
+ values() {
1600
+ return this.#nodes.values();
1601
+ }
1602
+ /**
1603
+ * Get all entries
1604
+ */
1605
+ entries() {
1606
+ return this.#nodes.entries();
1607
+ }
1608
+ /**
1609
+ * Fetch and cache node info
1610
+ * @param name Node name
1611
+ */
1612
+ async fetchInfo(name) {
1613
+ if (this.info.has(name)) {
1614
+ return this.info.get(name);
1615
+ }
1616
+ const node = this.#nodes.get(name);
1617
+ if (!node) {
1618
+ throw new Error(`Node '${name}' not found`);
1619
+ }
1620
+ const info = await node.rest.fetchInfo();
1621
+ this.info.set(name, info);
1622
+ return info;
1623
+ }
1624
+ /**
1625
+ * Check if a feature is supported by a node
1626
+ * @param type Feature type (filter, source, plugin)
1627
+ * @param value Feature value (name)
1628
+ * @param nodeName Optional node name (checks all if not specified)
1629
+ */
1630
+ supports(type, value, nodeName) {
1631
+ if (nodeName) {
1632
+ const node = this.#nodes.get(nodeName);
1633
+ if (!node) {
1634
+ return false;
1635
+ }
1636
+ return this.#checkNodeSupport(node.name, type, value);
1637
+ }
1638
+ return Array.from(this.#nodes.values()).some((node) => this.#checkNodeSupport(node.name, type, value));
1639
+ }
1640
+ /**
1641
+ * Check if a specific node supports a feature
1642
+ */
1643
+ #checkNodeSupport(nodeName, type, value) {
1644
+ const info = this.info.get(nodeName);
1645
+ if (!info) {
1646
+ return false;
1647
+ }
1648
+ switch (type) {
1649
+ case "filter":
1650
+ return info.filters?.includes(value) ?? false;
1651
+ case "source":
1652
+ return info.sourceManagers?.includes(value) ?? false;
1653
+ case "plugin":
1654
+ return info.plugins?.some((p) => p.name === value) ?? false;
1655
+ default:
1656
+ return false;
1657
+ }
1658
+ }
1659
+ /**
1660
+ * Handle node resumption
1661
+ * Sync all queues when a node resumes
1662
+ */
1663
+ async #handleResumed(node) {
1664
+ try {
1665
+ if (this.#player.options.autoSync) {
1666
+ await this.#player.queues.syncAll();
1667
+ }
1668
+ } catch (error) {
1669
+ this.emit("error", node, error);
1670
+ }
1671
+ }
1672
+ };
1673
+
1674
+ // src/voice/VoiceSession.ts
1675
+ var VoiceState = class {
1676
+ #changePromise = null;
1677
+ #node;
1678
+ #state;
1679
+ #player;
1680
+ guildId;
1681
+ player;
1682
+ constructor(player, node, guildId) {
1683
+ if (player.voices.has(guildId)) {
1684
+ throw new Error("An identical voice state already exists");
1685
+ }
1686
+ const _node = player.nodes.get(node);
1687
+ if (!_node) {
1688
+ throw new Error(`Node '${node}' not found`);
1689
+ }
1690
+ if (!_node.ready) {
1691
+ throw new Error(`Node '${node}' not ready`);
1692
+ }
1693
+ const state = player.voices[LookupSymbol](guildId);
1694
+ if (!state) {
1695
+ throw new Error(`No connection found for guild '${guildId}'`);
1696
+ }
1697
+ const _player = player.queues[LookupSymbol](guildId);
1698
+ if (!_player) {
1699
+ throw new Error(`No player found for guild '${guildId}'`);
1700
+ }
1701
+ this.#node = _node;
1702
+ this.#state = state;
1703
+ this.#player = _player;
1704
+ this.guildId = guildId;
1705
+ this.player = player;
1706
+ const immutable = {
1707
+ writable: false,
1708
+ configurable: false
1709
+ };
1710
+ Object.defineProperties(this, {
1711
+ guildId: immutable,
1712
+ player: { ...immutable, enumerable: false }
1713
+ });
1714
+ }
1715
+ /**
1716
+ * Current node for this voice connection
1717
+ */
1718
+ get node() {
1719
+ return this.#node;
1720
+ }
1721
+ /**
1722
+ * Ping to the voice server in milliseconds
1723
+ */
1724
+ get ping() {
1725
+ return this.#player.state.ping;
1726
+ }
1727
+ /**
1728
+ * Voice region ID (e.g., "us-west", "eu-central")
1729
+ */
1730
+ get regionId() {
1731
+ return this.#state.region_id;
1732
+ }
1733
+ /**
1734
+ * Voice channel ID
1735
+ */
1736
+ get channelId() {
1737
+ return this.#state.channel_id;
1738
+ }
1739
+ /**
1740
+ * Whether the bot is self-deafened
1741
+ */
1742
+ get selfDeaf() {
1743
+ return this.#state.self_deaf;
1744
+ }
1745
+ /**
1746
+ * Whether the bot is self-muted
1747
+ */
1748
+ get selfMute() {
1749
+ return this.#state.self_mute;
1750
+ }
1751
+ /**
1752
+ * Whether the bot is server-deafened
1753
+ */
1754
+ get serverDeaf() {
1755
+ return this.#state.deaf;
1756
+ }
1757
+ /**
1758
+ * Whether the bot is server-muted
1759
+ */
1760
+ get serverMute() {
1761
+ return this.#state.mute;
1762
+ }
1763
+ /**
1764
+ * Whether the bot is suppressed (priority speaker)
1765
+ */
1766
+ get suppressed() {
1767
+ return this.#state.suppress;
1768
+ }
1769
+ /**
1770
+ * Whether this voice state has been destroyed
1771
+ */
1772
+ get destroyed() {
1773
+ return this.player.voices.get(this.guildId) !== this;
1774
+ }
1775
+ /**
1776
+ * Whether the voice connection is fully connected
1777
+ */
1778
+ get connected() {
1779
+ if (!this.#player.state.connected) {
1780
+ return false;
1781
+ }
1782
+ return this.#state.connected && this.#state.node_session_id === this.#node.sessionId;
1783
+ }
1784
+ /**
1785
+ * Whether the voice connection is reconnecting
1786
+ */
1787
+ get reconnecting() {
1788
+ return this.#state.reconnecting;
1789
+ }
1790
+ /**
1791
+ * Whether the voice connection is disconnected
1792
+ */
1793
+ get disconnected() {
1794
+ return !this.connected && !this.reconnecting;
1795
+ }
1796
+ /**
1797
+ * Whether the voice connection is changing nodes
1798
+ */
1799
+ get changingNode() {
1800
+ return this.#changePromise !== null;
1801
+ }
1802
+ /**
1803
+ * Session ID for the voice connection
1804
+ */
1805
+ get sessionId() {
1806
+ return this.#state.session_id;
1807
+ }
1808
+ /**
1809
+ * Voice server token
1810
+ */
1811
+ get token() {
1812
+ return this.#state.token;
1813
+ }
1814
+ /**
1815
+ * Voice server endpoint
1816
+ */
1817
+ get endpoint() {
1818
+ return this.#state.endpoint;
1819
+ }
1820
+ /**
1821
+ * Node session ID this voice state is connected to
1822
+ */
1823
+ get nodeSessionId() {
1824
+ return this.#state.node_session_id;
1825
+ }
1826
+ /**
1827
+ * Destroy this voice connection
1828
+ * @param reason Optional reason for destruction
1829
+ */
1830
+ async destroy(reason) {
1831
+ return this.player.voices.destroy(this.guildId, reason);
1832
+ }
1833
+ /**
1834
+ * Connect to a voice channel
1835
+ * @param channelId Voice channel ID (defaults to current channel)
1836
+ */
1837
+ async connect(channelId = this.#state.channel_id) {
1838
+ await this.player.voices.connect(this.guildId, channelId);
1839
+ }
1840
+ /**
1841
+ * Disconnect from the voice channel
1842
+ */
1843
+ async disconnect() {
1844
+ return this.player.voices.disconnect(this.guildId);
1845
+ }
1846
+ /**
1847
+ * Change to a different Lavalink node
1848
+ * Preserves playback state and filters
1849
+ * @param name Name of the node to change to
1850
+ */
1851
+ async changeNode(name) {
1852
+ const node = this.player.nodes.get(name);
1853
+ if (!node) {
1854
+ throw new Error(`Node '${name}' not found`);
1855
+ }
1856
+ if (!node.ready) {
1857
+ throw new Error(`Node '${name}' not ready`);
1858
+ }
1859
+ if (this.#changePromise !== null) {
1860
+ return this.#changePromise;
1861
+ }
1862
+ if (name === this.#node.name) {
1863
+ throw new Error(`Already on node '${name}'`);
1864
+ }
1865
+ let resolve;
1866
+ let reject;
1867
+ const promise = new Promise((res, rej) => {
1868
+ resolve = res;
1869
+ reject = rej;
1870
+ });
1871
+ const resolver = { promise, resolve, reject };
1872
+ this.#changePromise = resolver.promise;
1873
+ const request = {
1874
+ voice: {
1875
+ channelId: this.#state.channel_id,
1876
+ endpoint: this.#state.endpoint,
1877
+ sessionId: this.#state.session_id,
1878
+ token: this.#state.token
1879
+ },
1880
+ filters: this.#player.filters,
1881
+ paused: this.#player.paused,
1882
+ volume: this.#player.volume
1883
+ };
1884
+ const track = this.#player.track;
1885
+ const wasPlaying = !this.#player.paused && track !== null;
1886
+ if (wasPlaying && this.player.nodes.supports("source", track.info.sourceName, node.name)) {
1887
+ request.track = { encoded: track.encoded, userData: track.userData };
1888
+ request.position = this.#player.state.position;
1889
+ }
1890
+ await this.#node.rest.destroyPlayer(this.guildId).catch(noop);
1891
+ const previousNode = this.#node;
1892
+ this.#node = node;
1893
+ try {
1894
+ const player = await node.rest.updatePlayer(this.guildId, request);
1895
+ this.#state.node_session_id = node.sessionId ?? "";
1896
+ Object.assign(this.#player, player);
1897
+ this.player.emit("voiceChange", this, previousNode, wasPlaying);
1898
+ resolver.resolve();
1899
+ } catch (err) {
1900
+ resolver.reject(err);
1901
+ throw err;
1902
+ } finally {
1903
+ this.#changePromise = null;
1904
+ }
1905
+ }
1906
+ /**
1907
+ * String representation of the voice state
1908
+ */
1909
+ toString() {
1910
+ return `VoiceState<${this.guildId}>`;
1911
+ }
1912
+ };
1913
+
1914
+ // src/voice/RegionSelector.ts
1915
+ var VoiceRegion = class {
1916
+ #pings = /* @__PURE__ */ new Map();
1917
+ id;
1918
+ player;
1919
+ constructor(player, regionId) {
1920
+ if (player.voices.regions.has(regionId)) {
1921
+ throw new Error("An identical voice region already exists");
1922
+ }
1923
+ this.id = regionId;
1924
+ this.player = player;
1925
+ const immutable = {
1926
+ writable: false,
1927
+ configurable: false
1928
+ };
1929
+ Object.defineProperties(this, {
1930
+ id: immutable,
1931
+ player: { ...immutable, enumerable: false }
1932
+ });
1933
+ }
1934
+ /**
1935
+ * Check if all ready nodes have ping data for this region
1936
+ * @returns `true` if all nodes are synced, `false` otherwise
1937
+ */
1938
+ inSync() {
1939
+ return !Array.from(this.player.nodes.values()).some((n) => n.ready && !this.#pings.has(n.name));
1940
+ }
1941
+ /**
1942
+ * Remove ping data for a node
1943
+ * @param name Node name
1944
+ * @returns `true` if data was removed, `false` if it didn't exist
1945
+ */
1946
+ forgetNode(name) {
1947
+ return this.#pings.delete(name);
1948
+ }
1949
+ /**
1950
+ * Get the average ping for a node in this region
1951
+ * @param name Node name
1952
+ * @returns Average ping in milliseconds, or `null` if no data
1953
+ */
1954
+ getAveragePing(name) {
1955
+ const pings = this.#pings.get(name)?.history;
1956
+ if (!pings?.length) {
1957
+ return null;
1958
+ }
1959
+ return Math.round(pings.reduce((total, current) => total + current, 0) / pings.length);
1960
+ }
1961
+ /**
1962
+ * Get all nodes with their average pings
1963
+ * @returns Array of [nodeName, averagePing] tuples
1964
+ */
1965
+ getAllPings() {
1966
+ return Array.from(this.player.nodes.values()).map((node) => [node.name, this.getAveragePing(node.name)]);
1967
+ }
1968
+ /**
1969
+ * Get the most relevant node for this region
1970
+ * Selects based on lowest average ping
1971
+ * @returns The node with lowest ping, or first relevant node if no ping data
1972
+ */
1973
+ getRelevantNode() {
1974
+ return this.player.nodes.relevant().sort((a, b) => {
1975
+ const pingA = this.getAveragePing(a.name) ?? Number.MAX_SAFE_INTEGER;
1976
+ const pingB = this.getAveragePing(b.name) ?? Number.MAX_SAFE_INTEGER;
1977
+ return pingA - pingB;
1978
+ })[0];
1979
+ }
1980
+ /**
1981
+ * Internal method to update ping statistics
1982
+ * Called by the voice manager when player state updates
1983
+ * @param name Node name
1984
+ * @param state Player state with ping information
1985
+ * @internal
1986
+ */
1987
+ [OnPingUpdateSymbol](name, state) {
1988
+ if (!state.connected) {
1989
+ return;
1990
+ }
1991
+ if (state.ping <= 0 || state.time <= 0) {
1992
+ return;
1993
+ }
1994
+ const pings = this.#pings.get(name);
1995
+ if (!pings) {
1996
+ this.#pings.set(name, {
1997
+ history: [state.ping],
1998
+ lastPingTime: state.time
1999
+ });
2000
+ return;
2001
+ }
2002
+ if (state.time - pings.lastPingTime < 12e3) {
2003
+ return;
2004
+ }
2005
+ pings.lastPingTime = state.time;
2006
+ pings.history.push(state.ping);
2007
+ if (pings.history.length > 5) {
2008
+ pings.history.shift();
2009
+ }
2010
+ }
2011
+ /**
2012
+ * Clear all ping data for this region
2013
+ */
2014
+ clear() {
2015
+ this.#pings.clear();
2016
+ }
2017
+ /**
2018
+ * Get the number of nodes with ping data
2019
+ */
2020
+ get nodeCount() {
2021
+ return this.#pings.size;
2022
+ }
2023
+ /**
2024
+ * String representation of the voice region
2025
+ */
2026
+ toString() {
2027
+ return `VoiceRegion<${this.id}>`;
2028
+ }
2029
+ /**
2030
+ * JSON representation of the voice region
2031
+ */
2032
+ toJSON() {
2033
+ return {
2034
+ id: this.id,
2035
+ inSync: this.inSync(),
2036
+ nodeCount: this.nodeCount,
2037
+ pings: Array.from(this.#pings.entries()).map(([node, stats]) => ({
2038
+ node,
2039
+ averagePing: this.getAveragePing(node),
2040
+ samples: stats.history.length
2041
+ }))
2042
+ };
2043
+ }
2044
+ };
2045
+
2046
+ // src/voice/VoiceConnection.ts
2047
+ var VoiceManager = class {
2048
+ #cache = /* @__PURE__ */ new Map();
2049
+ #voices = /* @__PURE__ */ new Map();
2050
+ #joins = /* @__PURE__ */ new Map();
2051
+ #destroys = /* @__PURE__ */ new Map();
2052
+ regions = /* @__PURE__ */ new Map();
2053
+ player;
2054
+ constructor(player) {
2055
+ if (player.voices === void 0) {
2056
+ this.player = player;
2057
+ } else {
2058
+ throw new Error("Manager already exists for this Player");
2059
+ }
2060
+ const immutable = {
2061
+ writable: false,
2062
+ configurable: false
2063
+ };
2064
+ Object.defineProperties(this, {
2065
+ regions: immutable,
2066
+ player: { ...immutable, enumerable: false }
2067
+ });
2068
+ }
2069
+ /**
2070
+ * Number of voice connections
2071
+ */
2072
+ get size() {
2073
+ return this.#voices.size;
2074
+ }
2075
+ /**
2076
+ * Get voice state for a guild
2077
+ */
2078
+ get(guildId) {
2079
+ return this.#voices.get(guildId);
2080
+ }
2081
+ /**
2082
+ * Check if voice state exists
2083
+ */
2084
+ has(guildId) {
2085
+ return this.#voices.has(guildId);
2086
+ }
2087
+ /**
2088
+ * Get all connected guild IDs
2089
+ */
2090
+ keys() {
2091
+ return this.#voices.keys();
2092
+ }
2093
+ /**
2094
+ * Get all voice connections
2095
+ */
2096
+ values() {
2097
+ return this.#voices.values();
2098
+ }
2099
+ /**
2100
+ * Get all voice connections as entries
2101
+ */
2102
+ entries() {
2103
+ return this.#voices.entries();
2104
+ }
2105
+ /**
2106
+ * Destroy a voice connection
2107
+ * @param guildId Guild ID
2108
+ * @param reason Reason for destruction
2109
+ */
2110
+ async destroy(guildId, reason = "destroyed") {
2111
+ if (this.player.queues.has(guildId)) {
2112
+ return this.player.queues.destroy(guildId, reason);
2113
+ }
2114
+ if (this.#destroys.has(guildId)) {
2115
+ return this.#destroys.get(guildId) ?? Promise.resolve();
2116
+ }
2117
+ const voice = this.#voices.get(guildId);
2118
+ if (!voice) {
2119
+ return;
2120
+ }
2121
+ let resolve;
2122
+ let reject;
2123
+ const promise = new Promise((res, rej) => {
2124
+ resolve = res;
2125
+ reject = rej;
2126
+ });
2127
+ const resolver = { promise, resolve, reject };
2128
+ this.#destroys.set(guildId, resolver.promise);
2129
+ if (this[LookupSymbol](guildId)?.connected) {
2130
+ await voice.disconnect().catch(noop);
2131
+ }
2132
+ this.#cache.delete(guildId);
2133
+ this.#voices.delete(guildId);
2134
+ this.player.emit("voiceDestroy", voice, reason);
2135
+ resolver.resolve();
2136
+ this.#destroys.delete(guildId);
2137
+ }
2138
+ /**
2139
+ * Connect to a voice channel
2140
+ * @param guildId Guild ID
2141
+ * @param voiceId Voice channel ID
2142
+ * @param options Connection options
2143
+ */
2144
+ async connect(guildId, voiceId, options) {
2145
+ if (!isString(guildId, SnowflakeRegex)) {
2146
+ throw new Error("Guild Id is not a valid Discord Id");
2147
+ }
2148
+ if (!isString(voiceId, SnowflakeRegex)) {
2149
+ throw new Error("Voice Id is not a valid Discord Id");
2150
+ }
2151
+ const currentRequest = this.#joins.get(guildId);
2152
+ if (currentRequest) {
2153
+ if (currentRequest.voiceId === voiceId) {
2154
+ return currentRequest.promise;
2155
+ }
2156
+ currentRequest.reject(new Error("Connection request was replaced"));
2157
+ }
2158
+ this.#joins.delete(guildId);
2159
+ let resolve;
2160
+ let reject;
2161
+ const promise = new Promise((res, rej) => {
2162
+ resolve = res;
2163
+ reject = rej;
2164
+ });
2165
+ const request = {
2166
+ promise,
2167
+ resolve,
2168
+ reject,
2169
+ voiceId,
2170
+ node: options?.node,
2171
+ context: options?.context,
2172
+ config: options ? { filters: options.filters, volume: options.volume } : void 0
2173
+ };
2174
+ this.#joins.set(guildId, request);
2175
+ await this.player.options.forwardVoiceUpdate(guildId, {
2176
+ op: 4,
2177
+ d: {
2178
+ guild_id: guildId,
2179
+ channel_id: voiceId,
2180
+ self_deaf: false,
2181
+ self_mute: false
2182
+ }
2183
+ });
2184
+ const timeout = setTimeout$2(15e3, void 0, { ref: false });
2185
+ const result = await Promise.race([request.promise, timeout]);
2186
+ if (result === void 0) {
2187
+ this.#joins.delete(guildId);
2188
+ throw new Error("Voice connection timed out");
2189
+ }
2190
+ return result;
2191
+ }
2192
+ /**
2193
+ * Disconnect from voice channel
2194
+ * @param guildId Guild ID
2195
+ */
2196
+ async disconnect(guildId) {
2197
+ const state = this.#cache.get(guildId);
2198
+ if (!state) {
2199
+ return;
2200
+ }
2201
+ await this.player.options.forwardVoiceUpdate(guildId, {
2202
+ op: 4,
2203
+ d: {
2204
+ guild_id: guildId,
2205
+ channel_id: null,
2206
+ self_deaf: false,
2207
+ self_mute: false
2208
+ }
2209
+ });
2210
+ state.channel_id = "";
2211
+ state.connected = false;
2212
+ }
2213
+ /**
2214
+ * Handle Discord dispatch events
2215
+ * @param payload Discord gateway payload
2216
+ */
2217
+ handleDispatch(payload) {
2218
+ if (payload.t === "READY") {
2219
+ this.#handleReady(payload);
2220
+ } else if (payload.t === "VOICE_STATE_UPDATE") {
2221
+ this.#handleVoiceStateUpdate(payload);
2222
+ } else if (payload.t === "VOICE_SERVER_UPDATE") {
2223
+ this.#handleVoiceServerUpdate(payload);
2224
+ }
2225
+ }
2226
+ /**
2227
+ * Internal lookup for voice state
2228
+ * @internal
2229
+ */
2230
+ [LookupSymbol](guildId) {
2231
+ return this.#cache.get(guildId);
2232
+ }
2233
+ /**
2234
+ * Internal update for voice state
2235
+ * @internal
2236
+ */
2237
+ [UpdateSymbol](guildId, partial) {
2238
+ const state = this.#cache.get(guildId);
2239
+ if (state) {
2240
+ Object.assign(state, partial);
2241
+ }
2242
+ }
2243
+ /**
2244
+ * Internal voice close handler
2245
+ * @internal
2246
+ */
2247
+ [OnVoiceCloseSymbol](guildId, code, reason, byRemote) {
2248
+ const voice = this.#voices.get(guildId);
2249
+ if (!voice) {
2250
+ return;
2251
+ }
2252
+ this.player.emit("voiceClose", voice, code, reason, byRemote);
2253
+ const shouldDestroy = code === 4014 /* Disconnected */ || code === 4021 /* DisconnectedRateLimited */ || code === 4022 /* DisconnectedCallTerminated */;
2254
+ if (shouldDestroy) {
2255
+ this.destroy(guildId, `Voice closed: ${reason} (${code})`).catch(noop);
2256
+ }
2257
+ }
2258
+ /**
2259
+ * Handle READY event
2260
+ */
2261
+ #handleReady(_payload) {
2262
+ this.#cache.clear();
2263
+ this.#voices.clear();
2264
+ this.#joins.clear();
2265
+ }
2266
+ /**
2267
+ * Handle VOICE_STATE_UPDATE event
2268
+ */
2269
+ #handleVoiceStateUpdate(payload) {
2270
+ const { d: data } = payload;
2271
+ const guildId = data.guild_id;
2272
+ if (!guildId || data.user_id !== this.player.clientId) {
2273
+ return;
2274
+ }
2275
+ let state = this.#cache.get(guildId);
2276
+ if (data.channel_id === null) {
2277
+ if (state) {
2278
+ state.channel_id = "";
2279
+ state.connected = false;
2280
+ }
2281
+ return;
2282
+ }
2283
+ if (!state) {
2284
+ state = {
2285
+ channel_id: data.channel_id,
2286
+ session_id: data.session_id,
2287
+ deaf: data.deaf,
2288
+ mute: data.mute,
2289
+ self_deaf: data.self_deaf,
2290
+ self_mute: data.self_mute,
2291
+ suppress: data.suppress,
2292
+ token: "",
2293
+ endpoint: "",
2294
+ connected: false,
2295
+ node_session_id: "",
2296
+ reconnecting: false,
2297
+ region_id: ""
2298
+ };
2299
+ this.#cache.set(guildId, state);
2300
+ } else {
2301
+ state.channel_id = data.channel_id;
2302
+ state.session_id = data.session_id;
2303
+ state.deaf = data.deaf;
2304
+ state.mute = data.mute;
2305
+ state.self_deaf = data.self_deaf;
2306
+ state.self_mute = data.self_mute;
2307
+ state.suppress = data.suppress;
2308
+ }
2309
+ void this.#tryConnect(guildId);
2310
+ }
2311
+ /**
2312
+ * Handle VOICE_SERVER_UPDATE event
2313
+ */
2314
+ #handleVoiceServerUpdate(payload) {
2315
+ const { d: data } = payload;
2316
+ const guildId = data.guild_id;
2317
+ let state = this.#cache.get(guildId);
2318
+ if (!state) {
2319
+ state = {
2320
+ channel_id: "",
2321
+ session_id: "",
2322
+ deaf: false,
2323
+ mute: false,
2324
+ self_deaf: false,
2325
+ self_mute: false,
2326
+ suppress: false,
2327
+ token: data.token,
2328
+ endpoint: data.endpoint ?? "",
2329
+ connected: false,
2330
+ node_session_id: "",
2331
+ reconnecting: false,
2332
+ region_id: ""
2333
+ };
2334
+ this.#cache.set(guildId, state);
2335
+ } else {
2336
+ state.token = data.token;
2337
+ state.endpoint = data.endpoint ?? "";
2338
+ }
2339
+ if (state.endpoint) {
2340
+ const match = state.endpoint.match(VoiceRegionIdRegex);
2341
+ if (match?.[1]) {
2342
+ state.region_id = match[1];
2343
+ if (!this.regions.has(state.region_id)) {
2344
+ this.regions.set(state.region_id, new VoiceRegion(this.player, state.region_id));
2345
+ }
2346
+ }
2347
+ }
2348
+ void this.#tryConnect(guildId);
2349
+ }
2350
+ /**
2351
+ * Try to establish voice connection
2352
+ */
2353
+ async #tryConnect(guildId) {
2354
+ const state = this.#cache.get(guildId);
2355
+ const request = this.#joins.get(guildId);
2356
+ if (!state || !request) {
2357
+ return;
2358
+ }
2359
+ if (!state.channel_id || !state.session_id || !state.token || !state.endpoint) {
2360
+ return;
2361
+ }
2362
+ this.#joins.delete(guildId);
2363
+ try {
2364
+ const nodeName = request.node ?? (state.region_id && this.regions.get(state.region_id)?.getRelevantNode()?.name) ?? this.player.nodes.relevant()[0]?.name;
2365
+ if (!nodeName) {
2366
+ throw new Error("No nodes available");
2367
+ }
2368
+ let queue = this.player.queues.get(guildId);
2369
+ if (!queue) {
2370
+ queue = await this.player.queues.create({
2371
+ guildId,
2372
+ voiceId: state.channel_id,
2373
+ node: nodeName,
2374
+ context: request.context,
2375
+ ...request.config
2376
+ });
2377
+ }
2378
+ const voice = new VoiceState(this.player, nodeName, guildId);
2379
+ this.#voices.set(guildId, voice);
2380
+ state.connected = true;
2381
+ state.node_session_id = voice.node.sessionId ?? "";
2382
+ this.player.emit("voiceConnect", voice);
2383
+ request.resolve(voice);
2384
+ } catch (error) {
2385
+ request.reject(error instanceof Error ? error : new Error(String(error)));
2386
+ }
2387
+ }
2388
+ };
2389
+
2390
+ // src/audio/AudioTrack.ts
2391
+ var Track = class _Track {
2392
+ /**
2393
+ * Unique identifier of the track
2394
+ */
2395
+ id;
2396
+ /**
2397
+ * Title of the track
2398
+ */
2399
+ title = "Unknown Track";
2400
+ /**
2401
+ * Author/artist of the track
2402
+ */
2403
+ author = "Unknown Author";
2404
+ /**
2405
+ * Whether the track is a live stream
2406
+ */
2407
+ isLive = false;
2408
+ /**
2409
+ * Whether the track is seekable
2410
+ */
2411
+ isSeekable = false;
2412
+ /**
2413
+ * Duration of the track in milliseconds
2414
+ */
2415
+ duration = 0;
2416
+ /**
2417
+ * Formatted duration string (hh:mm:ss or mm:ss)
2418
+ */
2419
+ formattedDuration = "00:00";
2420
+ /**
2421
+ * Uniform Resource Identifier of the track
2422
+ */
2423
+ uri = null;
2424
+ /**
2425
+ * International Standard Recording Code
2426
+ */
2427
+ isrc = null;
2428
+ /**
2429
+ * URL of the track (validated URI)
2430
+ */
2431
+ url = null;
2432
+ /**
2433
+ * Artwork/thumbnail URL
2434
+ */
2435
+ artworkUrl = null;
2436
+ /**
2437
+ * Custom user data attached to the track
2438
+ */
2439
+ userData = {};
2440
+ /**
2441
+ * Additional info from plugins
2442
+ */
2443
+ pluginInfo = {};
2444
+ /**
2445
+ * Encoded string representation (Lavalink format)
2446
+ */
2447
+ encoded;
2448
+ /**
2449
+ * Source name (youtube, spotify, soundcloud, etc.)
2450
+ */
2451
+ sourceName = "unknown";
2452
+ get identifier() {
2453
+ return this.id;
2454
+ }
2455
+ get stream() {
2456
+ return this.isLive;
2457
+ }
2458
+ get seekable() {
2459
+ return this.isSeekable;
2460
+ }
2461
+ get durationFormatted() {
2462
+ return this.formattedDuration;
2463
+ }
2464
+ get source() {
2465
+ return this.sourceName;
2466
+ }
2467
+ get thumbnail() {
2468
+ return this.artworkUrl;
2469
+ }
2470
+ get info() {
2471
+ return {
2472
+ identifier: this.id,
2473
+ position: 0,
2474
+ title: this.title,
2475
+ author: this.author,
2476
+ length: this.duration,
2477
+ isStream: this.isLive,
2478
+ isSeekable: this.isSeekable,
2479
+ uri: this.uri,
2480
+ isrc: this.isrc,
2481
+ artworkUrl: this.artworkUrl,
2482
+ sourceName: this.sourceName
2483
+ };
2484
+ }
2485
+ constructor(data) {
2486
+ if (!isRecord(data)) {
2487
+ throw new Error("Track data must be an object");
2488
+ }
2489
+ if (!isRecord(data.info)) {
2490
+ throw new Error("Track info is not an object");
2491
+ }
2492
+ if (isString(data.info.identifier, "non-empty")) {
2493
+ this.id = data.info.identifier;
2494
+ } else {
2495
+ throw new Error("Track does not have an identifier");
2496
+ }
2497
+ if (isString(data.encoded, "non-empty")) {
2498
+ this.encoded = data.encoded;
2499
+ } else {
2500
+ throw new Error("Track does not have an encoded data string");
2501
+ }
2502
+ if (isString(data.info.title, "non-empty")) {
2503
+ this.title = data.info.title;
2504
+ }
2505
+ if (isString(data.info.author, "non-empty")) {
2506
+ this.author = data.info.author;
2507
+ }
2508
+ if (data.info.isStream) {
2509
+ this.isLive = true;
2510
+ }
2511
+ if (data.info.isSeekable) {
2512
+ this.isSeekable = true;
2513
+ }
2514
+ if (this.isLive) {
2515
+ this.duration = Number.POSITIVE_INFINITY;
2516
+ this.formattedDuration = "Live";
2517
+ } else if (isNumber(data.info.length, "whole")) {
2518
+ this.duration = data.info.length;
2519
+ this.formattedDuration = formatDuration(this.duration);
2520
+ }
2521
+ if (isString(data.info.uri, "non-empty")) {
2522
+ this.uri = data.info.uri;
2523
+ }
2524
+ if (isString(data.info.isrc, "non-empty")) {
2525
+ this.isrc = data.info.isrc;
2526
+ }
2527
+ if (isString(this.uri, "url")) {
2528
+ this.url = this.uri;
2529
+ }
2530
+ if (isString(data.info.artworkUrl, "url")) {
2531
+ this.artworkUrl = data.info.artworkUrl;
2532
+ }
2533
+ if (isRecord(data.userData, "non-empty")) {
2534
+ this.userData = data.userData;
2535
+ }
2536
+ if (isRecord(data.pluginInfo, "non-empty")) {
2537
+ this.pluginInfo = data.pluginInfo;
2538
+ }
2539
+ if (isString(data.info.sourceName, "non-empty")) {
2540
+ this.sourceName = data.info.sourceName;
2541
+ }
2542
+ }
2543
+ /**
2544
+ * String representation of the track
2545
+ */
2546
+ toString() {
2547
+ return this.title;
2548
+ }
2549
+ /**
2550
+ * JSON representation of the track
2551
+ */
2552
+ toJSON() {
2553
+ return {
2554
+ identifier: this.id,
2555
+ title: this.title,
2556
+ author: this.author,
2557
+ duration: this.duration,
2558
+ uri: this.uri,
2559
+ thumbnail: this.artworkUrl,
2560
+ source: this.sourceName,
2561
+ isSeekable: this.isSeekable,
2562
+ isStream: this.isLive,
2563
+ encoded: this.encoded,
2564
+ userData: this.userData,
2565
+ pluginInfo: this.pluginInfo
2566
+ };
2567
+ }
2568
+ /**
2569
+ * Create a clone of the track
2570
+ */
2571
+ clone() {
2572
+ return new _Track({
2573
+ encoded: this.encoded,
2574
+ info: {
2575
+ ...this.info,
2576
+ length: this.duration,
2577
+ isStream: this.isLive
2578
+ },
2579
+ userData: this.userData,
2580
+ pluginInfo: this.pluginInfo
2581
+ });
2582
+ }
2583
+ };
2584
+
2585
+ // src/audio/TrackCollection.ts
2586
+ var Playlist = class {
2587
+ /**
2588
+ * Name of the playlist
2589
+ */
2590
+ name = "Unknown Playlist";
2591
+ /**
2592
+ * Index of the track that was selected (from URL)
2593
+ */
2594
+ selectedTrack = -1;
2595
+ /**
2596
+ * List of tracks in the playlist
2597
+ */
2598
+ tracks = [];
2599
+ /**
2600
+ * Additional info from plugins
2601
+ */
2602
+ pluginInfo = {};
2603
+ /**
2604
+ * Total duration of all tracks in milliseconds
2605
+ */
2606
+ duration = 0;
2607
+ /**
2608
+ * Formatted total duration string
2609
+ */
2610
+ formattedDuration = "00:00";
2611
+ constructor(data) {
2612
+ if (!isRecord(data)) {
2613
+ throw new Error("Playlist data must be an object");
2614
+ }
2615
+ if (!isRecord(data.info)) {
2616
+ throw new Error("Playlist info is not an object");
2617
+ }
2618
+ if (!isArray(data.tracks)) {
2619
+ throw new Error("Playlist tracks must be an array");
2620
+ }
2621
+ for (let i = 0; i < data.tracks.length; i++) {
2622
+ const track = new Track(data.tracks[i]);
2623
+ if (!track.isLive) {
2624
+ this.duration += track.duration;
2625
+ }
2626
+ this.tracks.push(track);
2627
+ }
2628
+ if (isString(data.info.name, "non-empty")) {
2629
+ this.name = data.info.name;
2630
+ } else if (data.info.name === "") {
2631
+ this.name = "";
2632
+ }
2633
+ if (isNumber(data.info.selectedTrack, "whole")) {
2634
+ this.selectedTrack = data.info.selectedTrack;
2635
+ }
2636
+ if (isRecord(data.pluginInfo, "non-empty")) {
2637
+ this.pluginInfo = data.pluginInfo;
2638
+ }
2639
+ if (this.duration > 0) {
2640
+ this.formattedDuration = formatDuration(this.duration);
2641
+ }
2642
+ }
2643
+ get selected() {
2644
+ if (this.selectedTrack < 0 || this.selectedTrack >= this.tracks.length) {
2645
+ return null;
2646
+ }
2647
+ return this.tracks[this.selectedTrack] ?? null;
2648
+ }
2649
+ /**
2650
+ * Get the selected track (if any)
2651
+ * @returns The selected track or undefined if none/invalid
2652
+ */
2653
+ getSelectedTrack() {
2654
+ return this.selected ?? void 0;
2655
+ }
2656
+ /**
2657
+ * Get the number of tracks
2658
+ */
2659
+ get length() {
2660
+ return this.tracks.length;
2661
+ }
2662
+ /**
2663
+ * Get the number of tracks (alias for length)
2664
+ */
2665
+ get trackCount() {
2666
+ return this.tracks.length;
2667
+ }
2668
+ /**
2669
+ * Formatted total duration (alias for formattedDuration)
2670
+ */
2671
+ get durationFormatted() {
2672
+ return this.formattedDuration;
2673
+ }
2674
+ /**
2675
+ * String representation of the playlist
2676
+ */
2677
+ toString() {
2678
+ return this.name;
2679
+ }
2680
+ /**
2681
+ * JSON representation of the playlist
2682
+ */
2683
+ toJSON() {
2684
+ return {
2685
+ name: this.name,
2686
+ selectedTrack: this.selectedTrack,
2687
+ tracks: this.tracks.map((t) => t.toJSON()),
2688
+ duration: this.duration,
2689
+ formattedDuration: this.formattedDuration,
2690
+ pluginInfo: this.pluginInfo
2691
+ };
2692
+ }
2693
+ };
2694
+
2695
+ // src/audio/AudioFilters.ts
2696
+ var FilterManager = class {
2697
+ #player;
2698
+ #guildId;
2699
+ constructor(player, guildId) {
2700
+ this.#player = player;
2701
+ this.#guildId = guildId;
2702
+ }
2703
+ /**
2704
+ * Get current filter data
2705
+ */
2706
+ get data() {
2707
+ const queue = this.#player.queues?.get(this.#guildId);
2708
+ if (!queue) {
2709
+ return {};
2710
+ }
2711
+ return queue["#player"]?.filters || {};
2712
+ }
2713
+ /**
2714
+ * Get a specific filter
2715
+ */
2716
+ get(key) {
2717
+ if (key === "pluginFilters") {
2718
+ return void 0;
2719
+ }
2720
+ return this.data[key];
2721
+ }
2722
+ /**
2723
+ * Set a filter
2724
+ */
2725
+ async set(key, value) {
2726
+ const queue = this.#player.queues?.get(this.#guildId);
2727
+ if (!queue) {
2728
+ throw new Error("Queue not found");
2729
+ }
2730
+ const filters = { ...this.data, [key]: value };
2731
+ await queue["#update"]({ filters });
2732
+ }
2733
+ /**
2734
+ * Set volume
2735
+ */
2736
+ async setVolume(volume) {
2737
+ const clamped = Math.max(0, Math.min(1, volume));
2738
+ await this.set("volume", clamped);
2739
+ }
2740
+ /**
2741
+ * Get volume
2742
+ */
2743
+ get volume() {
2744
+ return this.get("volume") ?? 1;
2745
+ }
2746
+ /**
2747
+ * Set equalizer bands
2748
+ */
2749
+ async setEqualizer(bands) {
2750
+ await this.set("equalizer", bands);
2751
+ }
2752
+ /**
2753
+ * Get equalizer bands
2754
+ */
2755
+ get equalizer() {
2756
+ return this.get("equalizer") ?? [];
2757
+ }
2758
+ /**
2759
+ * Set karaoke filter
2760
+ */
2761
+ async setKaraoke(karaoke) {
2762
+ await this.set("karaoke", karaoke);
2763
+ }
2764
+ /**
2765
+ * Get karaoke filter
2766
+ */
2767
+ get karaoke() {
2768
+ return this.get("karaoke");
2769
+ }
2770
+ /**
2771
+ * Set timescale filter
2772
+ */
2773
+ async setTimescale(timescale) {
2774
+ await this.set("timescale", timescale);
2775
+ }
2776
+ /**
2777
+ * Get timescale filter
2778
+ */
2779
+ get timescale() {
2780
+ return this.get("timescale");
2781
+ }
2782
+ /**
2783
+ * Set tremolo filter
2784
+ */
2785
+ async setTremolo(tremolo) {
2786
+ await this.set("tremolo", tremolo);
2787
+ }
2788
+ /**
2789
+ * Get tremolo filter
2790
+ */
2791
+ get tremolo() {
2792
+ return this.get("tremolo");
2793
+ }
2794
+ /**
2795
+ * Set vibrato filter
2796
+ */
2797
+ async setVibrato(vibrato) {
2798
+ await this.set("vibrato", vibrato);
2799
+ }
2800
+ /**
2801
+ * Get vibrato filter
2802
+ */
2803
+ get vibrato() {
2804
+ return this.get("vibrato");
2805
+ }
2806
+ /**
2807
+ * Set rotation filter
2808
+ */
2809
+ async setRotation(rotation) {
2810
+ await this.set("rotation", rotation);
2811
+ }
2812
+ /**
2813
+ * Get rotation filter
2814
+ */
2815
+ get rotation() {
2816
+ return this.get("rotation");
2817
+ }
2818
+ /**
2819
+ * Clear specific equalizer filter
2820
+ */
2821
+ async clearEqualizer() {
2822
+ await this.delete("equalizer");
2823
+ }
2824
+ /**
2825
+ * Clear specific karaoke filter
2826
+ */
2827
+ async clearKaraoke() {
2828
+ await this.delete("karaoke");
2829
+ }
2830
+ /**
2831
+ * Clear specific timescale filter
2832
+ */
2833
+ async clearTimescale() {
2834
+ await this.delete("timescale");
2835
+ }
2836
+ /**
2837
+ * Clear specific tremolo filter
2838
+ */
2839
+ async clearTremolo() {
2840
+ await this.delete("tremolo");
2841
+ }
2842
+ /**
2843
+ * Clear specific vibrato filter
2844
+ */
2845
+ async clearVibrato() {
2846
+ await this.delete("vibrato");
2847
+ }
2848
+ /**
2849
+ * Clear specific rotation filter
2850
+ */
2851
+ async clearRotation() {
2852
+ await this.delete("rotation");
2853
+ }
2854
+ /**
2855
+ * Clear all filters
2856
+ */
2857
+ async clearAll() {
2858
+ await this.clear();
2859
+ }
2860
+ /**
2861
+ * Check if a filter is set
2862
+ */
2863
+ has(key) {
2864
+ const value = this.data[key];
2865
+ return value !== void 0 && value !== null;
2866
+ }
2867
+ /**
2868
+ * Delete a specific filter
2869
+ */
2870
+ async delete(key) {
2871
+ if (!this.has(key)) {
2872
+ return false;
2873
+ }
2874
+ const queue = this.#player.queues?.get(this.#guildId);
2875
+ if (!queue) {
2876
+ throw new Error("Queue not found");
2877
+ }
2878
+ const filters = { ...this.data };
2879
+ delete filters[key];
2880
+ await queue["#update"]({ filters });
2881
+ return true;
2882
+ }
2883
+ /**
2884
+ * Clear filters
2885
+ * @param type - "native" to clear only native filters, "plugin" to clear only plugin filters, undefined to clear all
2886
+ */
2887
+ async clear(type) {
2888
+ const queue = this.#player.queues?.get(this.#guildId);
2889
+ if (!queue) {
2890
+ throw new Error("Queue not found");
2891
+ }
2892
+ let filters = {};
2893
+ if (type === "native") {
2894
+ if (this.data.pluginFilters) {
2895
+ filters.pluginFilters = this.data.pluginFilters;
2896
+ }
2897
+ } else if (type === "plugin") {
2898
+ const { pluginFilters: _, ...nativeFilters } = this.data;
2899
+ filters = nativeFilters;
2900
+ }
2901
+ await queue["#update"]({ filters });
2902
+ }
2903
+ /**
2904
+ * Apply multiple filters at once
2905
+ */
2906
+ async apply(filters) {
2907
+ const queue = this.#player.queues?.get(this.#guildId);
2908
+ if (!queue) {
2909
+ throw new Error("Queue not found");
2910
+ }
2911
+ const newFilters = { ...this.data, ...filters };
2912
+ await queue["#update"]({ filters: newFilters });
2913
+ }
2914
+ /**
2915
+ * Set an EQ preset by name
2916
+ * @param preset The preset name
2917
+ * @returns Promise that resolves when the preset is applied
2918
+ */
2919
+ async setEQPreset(preset) {
2920
+ const bands = EQPresets[preset];
2921
+ if (!bands) {
2922
+ throw new Error(`Invalid EQ preset: ${preset}`);
2923
+ }
2924
+ await this.set("equalizer", bands);
2925
+ }
2926
+ /**
2927
+ * Get all available EQ preset names
2928
+ * @returns Array of preset names
2929
+ */
2930
+ getEQPresetNames() {
2931
+ return Object.keys(EQPresets);
2932
+ }
2933
+ /**
2934
+ * Check if a preset name is valid
2935
+ * @param name The preset name to check
2936
+ * @returns True if the preset exists
2937
+ */
2938
+ isValidEQPreset(name) {
2939
+ return name in EQPresets;
2940
+ }
2941
+ };
2942
+
2943
+ // src/audio/AudioQueue.ts
2944
+ var Queue = class {
2945
+ #player;
2946
+ #autoplay = false;
2947
+ #repeatMode = "none";
2948
+ #tracks = [];
2949
+ #previousTracks = [];
2950
+ context = {};
2951
+ voice;
2952
+ filters;
2953
+ player;
2954
+ constructor(player, guildId, context) {
2955
+ if (player.queues.has(guildId)) {
2956
+ throw new Error("An identical queue already exists");
2957
+ }
2958
+ const _player = player.queues[LookupSymbol](guildId);
2959
+ if (!_player) {
2960
+ throw new Error(`No player found for guild '${guildId}'`);
2961
+ }
2962
+ const voice = player.voices.get(guildId);
2963
+ if (!voice) {
2964
+ throw new Error(`No connection found for guild '${guildId}'`);
2965
+ }
2966
+ this.#player = _player;
2967
+ if (context !== void 0) {
2968
+ this.context = context;
2969
+ }
2970
+ this.voice = voice;
2971
+ this.filters = new FilterManager(player, guildId);
2972
+ this.player = player;
2973
+ const immutable = {
2974
+ writable: false,
2975
+ configurable: false
2976
+ };
2977
+ Object.defineProperties(this, {
2978
+ voice: immutable,
2979
+ filters: immutable,
2980
+ player: { ...immutable, enumerable: false }
2981
+ });
2982
+ }
2983
+ get node() {
2984
+ return this.voice.nodeSessionId ? this.player.nodes.all.find((n) => n.sessionId === this.voice.nodeSessionId) : this.player.nodes.relevant()[0];
2985
+ }
2986
+ get rest() {
2987
+ return this.node?.rest;
2988
+ }
2989
+ get guildId() {
2990
+ return this.voice.guildId;
2991
+ }
2992
+ get volume() {
2993
+ return this.#player.volume;
2994
+ }
2995
+ get paused() {
2996
+ return this.#player.paused;
2997
+ }
2998
+ get stopped() {
2999
+ return this.track !== null && this.#player.track === null;
3000
+ }
3001
+ get empty() {
3002
+ return this.finished && !this.hasPrevious;
3003
+ }
3004
+ get playing() {
3005
+ return !this.paused && this.track !== null && this.#player.track !== null;
3006
+ }
3007
+ get autoplay() {
3008
+ return this.#autoplay;
3009
+ }
3010
+ get finished() {
3011
+ return this.#tracks.length === 0;
3012
+ }
3013
+ get destroyed() {
3014
+ return this.player.queues.get(this.guildId) !== this;
3015
+ }
3016
+ get repeatMode() {
3017
+ return this.#repeatMode;
3018
+ }
3019
+ get hasNext() {
3020
+ return this.#tracks.length > 1;
3021
+ }
3022
+ get hasPrevious() {
3023
+ return this.#previousTracks.length !== 0;
3024
+ }
3025
+ get track() {
3026
+ return this.#tracks[0] ?? null;
3027
+ }
3028
+ get previousTrack() {
3029
+ return this.#previousTracks[this.#previousTracks.length - 1] ?? null;
3030
+ }
3031
+ get tracks() {
3032
+ return this.#tracks;
3033
+ }
3034
+ get previousTracks() {
3035
+ return this.#previousTracks;
3036
+ }
3037
+ get length() {
3038
+ return this.#tracks.length;
3039
+ }
3040
+ get totalLength() {
3041
+ return this.length + this.#previousTracks.length;
3042
+ }
3043
+ get duration() {
3044
+ return this.#tracks.reduce((time, track) => time + (track.isLive ? 0 : track.duration), 0);
3045
+ }
3046
+ get formattedDuration() {
3047
+ return this.#formatDuration(this.duration);
3048
+ }
3049
+ get currentTime() {
3050
+ if (this.#player.paused || !this.#player.state.connected) {
3051
+ return this.#player.state.position;
3052
+ }
3053
+ if (this.#player.state.position === 0) {
3054
+ return 0;
3055
+ }
3056
+ return this.#player.state.position + (Date.now() - this.#player.state.time);
3057
+ }
3058
+ get formattedCurrentTime() {
3059
+ return this.#formatDuration(this.currentTime);
3060
+ }
3061
+ #formatDuration(ms) {
3062
+ const seconds = Math.floor(ms / 1e3);
3063
+ const minutes = Math.floor(seconds / 60);
3064
+ const hours = Math.floor(minutes / 60);
3065
+ if (hours > 0) {
3066
+ return `${hours}:${(minutes % 60).toString().padStart(2, "0")}:${(seconds % 60).toString().padStart(2, "0")}`;
3067
+ }
3068
+ return `${minutes}:${(seconds % 60).toString().padStart(2, "0")}`;
3069
+ }
3070
+ #error(data) {
3071
+ const explicit = typeof data === "string";
3072
+ const message = explicit ? data : data.message ?? data.cause ?? "Unknown error";
3073
+ const error = new Error(message);
3074
+ error.name = `Error [${this.constructor.name}]`;
3075
+ if (!explicit && data.severity) {
3076
+ error.severity = data.severity;
3077
+ }
3078
+ return error;
3079
+ }
3080
+ async #update(data, params) {
3081
+ const node = this.node;
3082
+ if (!node) {
3083
+ throw this.#error("No node available");
3084
+ }
3085
+ const player = await node.rest.updatePlayer(this.guildId, data, params);
3086
+ Object.assign(this.#player, player);
3087
+ }
3088
+ /**
3089
+ * Sync queue state with Lavalink
3090
+ * @param target - "local" to pull from Lavalink, "remote" to push to Lavalink
3091
+ */
3092
+ async sync(target = "local") {
3093
+ const node = this.node;
3094
+ if (!node) {
3095
+ throw this.#error("No node available");
3096
+ }
3097
+ if (target === "local") {
3098
+ const player = await node.rest.fetchPlayer(this.guildId);
3099
+ Object.assign(this.#player, player);
3100
+ return;
3101
+ }
3102
+ if (target !== "remote") {
3103
+ throw this.#error("Target must be 'local' or 'remote'");
3104
+ }
3105
+ const voice = this.player.voices[LookupSymbol](this.guildId);
3106
+ if (!voice) {
3107
+ return;
3108
+ }
3109
+ const request = {
3110
+ voice: {
3111
+ token: voice.token,
3112
+ endpoint: voice.endpoint,
3113
+ sessionId: voice.session_id,
3114
+ channelId: voice.channel_id
3115
+ },
3116
+ filters: this.#player.filters,
3117
+ paused: this.#player.paused,
3118
+ volume: this.#player.volume
3119
+ };
3120
+ if (this.#player.track !== null) {
3121
+ request.track = {
3122
+ encoded: this.#player.track.encoded,
3123
+ userData: this.#player.track.userData
3124
+ };
3125
+ request.position = this.#player.state.position;
3126
+ }
3127
+ await this.#update(request);
3128
+ const nodeSessionId = this.node?.sessionId ?? "";
3129
+ this.player.voices[UpdateSymbol](this.guildId, {
3130
+ node_session_id: nodeSessionId
3131
+ });
3132
+ }
3133
+ /**
3134
+ * Search for tracks
3135
+ */
3136
+ async search(query, prefix = this.player.options.queryPrefix) {
3137
+ return this.player.search(query, { prefix, node: this.node?.name });
3138
+ }
3139
+ /**
3140
+ * Add tracks to the queue
3141
+ */
3142
+ add(source, userData) {
3143
+ const added = [];
3144
+ if (source instanceof Track) {
3145
+ Object.assign(source.userData, userData);
3146
+ this.#tracks.push(source);
3147
+ added.push(source);
3148
+ } else if (source instanceof Playlist) {
3149
+ for (const track of source.tracks) {
3150
+ Object.assign(track.userData, userData);
3151
+ this.#tracks.push(track);
3152
+ added.push(track);
3153
+ }
3154
+ } else if (Array.isArray(source)) {
3155
+ for (const track of source) {
3156
+ if (track instanceof Track) {
3157
+ Object.assign(track.userData, userData);
3158
+ this.#tracks.push(track);
3159
+ added.push(track);
3160
+ }
3161
+ }
3162
+ } else {
3163
+ throw this.#error("Source must be a track, playlist, or array of tracks");
3164
+ }
3165
+ this.player.emit("trackAdd", this.player, this.guildId, added);
3166
+ return this;
3167
+ }
3168
+ /**
3169
+ * Add related tracks (for autoplay)
3170
+ */
3171
+ async addRelated(refTrack) {
3172
+ refTrack ??= this.track ?? this.previousTrack ?? void 0;
3173
+ if (!refTrack) {
3174
+ throw this.#error("The queue is empty and there is no track to refer");
3175
+ }
3176
+ if (!this.node) {
3177
+ return [];
3178
+ }
3179
+ const relatedTracks = await this.player.options.fetchRelatedTracks?.(this, refTrack);
3180
+ this.add(relatedTracks);
3181
+ return relatedTracks;
3182
+ }
3183
+ remove(input) {
3184
+ if (typeof input === "number") {
3185
+ if (input === 0 && !this.stopped) {
3186
+ return;
3187
+ }
3188
+ if (input < 0) {
3189
+ return this.#previousTracks.splice(input, 1)[0];
3190
+ }
3191
+ return this.#tracks.splice(input, 1)[0];
3192
+ }
3193
+ if (Array.isArray(input)) {
3194
+ if (input.length === 0) {
3195
+ return [];
3196
+ }
3197
+ const tracks = [];
3198
+ const indices = input.toSorted((a, b) => a - b);
3199
+ for (let i = 0; i < indices.length; i++) {
3200
+ const index = indices[i] - i;
3201
+ if (index === 0 && !this.stopped) {
3202
+ continue;
3203
+ }
3204
+ if (index < 0) {
3205
+ tracks.push(...this.#previousTracks.splice(index, 1));
3206
+ } else if (index < this.#tracks.length) {
3207
+ tracks.push(...this.#tracks.splice(index, 1));
3208
+ }
3209
+ }
3210
+ return tracks;
3211
+ }
3212
+ throw this.#error("Input must be an index or array of indices");
3213
+ }
3214
+ /**
3215
+ * Clear tracks from the queue
3216
+ */
3217
+ clear(type) {
3218
+ switch (type) {
3219
+ case "current":
3220
+ if (!this.finished) {
3221
+ this.#tracks.length = this.stopped ? 0 : 1;
3222
+ }
3223
+ break;
3224
+ case "previous":
3225
+ this.#previousTracks.length = 0;
3226
+ break;
3227
+ default:
3228
+ if (!this.finished) {
3229
+ this.#tracks.length = this.stopped ? 0 : 1;
3230
+ }
3231
+ this.#previousTracks.length = 0;
3232
+ }
3233
+ }
3234
+ /**
3235
+ * Jump to a specific track
3236
+ */
3237
+ async jump(index) {
3238
+ if (this.empty) {
3239
+ throw this.#error("The queue is empty at the moment");
3240
+ }
3241
+ if (!Number.isInteger(index)) {
3242
+ throw this.#error("Index must be an integer");
3243
+ }
3244
+ const track = index < 0 ? this.#previousTracks[this.#previousTracks.length + index] : this.#tracks[index];
3245
+ if (!track) {
3246
+ throw this.#error("Specified index is out of range");
3247
+ }
3248
+ if (index < 0) {
3249
+ this.#tracks.unshift(...this.#previousTracks.splice(index));
3250
+ } else {
3251
+ this.#previousTracks.push(...this.#tracks.splice(0, index));
3252
+ }
3253
+ await this.#update({
3254
+ paused: false,
3255
+ track: { encoded: track.encoded, userData: track.userData }
3256
+ });
3257
+ return track;
3258
+ }
3259
+ /**
3260
+ * Pause playback
3261
+ */
3262
+ async pause() {
3263
+ await this.#update({ paused: true });
3264
+ return this.#player.paused;
3265
+ }
3266
+ /**
3267
+ * Resume playback
3268
+ */
3269
+ async resume() {
3270
+ if (this.stopped) {
3271
+ await this.jump(0);
3272
+ } else {
3273
+ await this.#update({ paused: false });
3274
+ }
3275
+ return !this.#player.paused;
3276
+ }
3277
+ /**
3278
+ * Seek to a position
3279
+ */
3280
+ async seek(ms) {
3281
+ if (this.track === null) {
3282
+ throw this.#error("No track is playing at the moment");
3283
+ }
3284
+ if (!this.track.isSeekable) {
3285
+ throw this.#error("Current track is not seekable");
3286
+ }
3287
+ if (!Number.isInteger(ms) || ms < 0) {
3288
+ throw this.#error("Seek time must be a positive integer");
3289
+ }
3290
+ if (ms > this.track.duration) {
3291
+ throw this.#error("Specified time to seek is out of range");
3292
+ }
3293
+ const body = { paused: false, position: ms };
3294
+ if (this.#player.track?.info.identifier !== this.track.id) {
3295
+ body.track = { encoded: this.track.encoded, userData: this.track.userData };
3296
+ }
3297
+ await this.#update(body);
3298
+ return this.#player.state.position;
3299
+ }
3300
+ /**
3301
+ * Play next track
3302
+ */
3303
+ async next() {
3304
+ if (this.hasNext) {
3305
+ return this.jump(1);
3306
+ }
3307
+ if (this.hasPrevious && this.#repeatMode === "queue") {
3308
+ const track = this.#previousTracks.shift();
3309
+ if (track) {
3310
+ this.#tracks.push(track);
3311
+ }
3312
+ return this.jump(this.hasNext ? 1 : 0);
3313
+ }
3314
+ if (!this.empty && this.#autoplay) {
3315
+ const related = await this.addRelated();
3316
+ if (related.length > 0) {
3317
+ return this.jump(this.length - related.length);
3318
+ }
3319
+ }
3320
+ if (!this.finished) {
3321
+ const track = this.#tracks.shift();
3322
+ if (track) {
3323
+ this.#previousTracks.push(track);
3324
+ }
3325
+ await this.stop();
3326
+ }
3327
+ return null;
3328
+ }
3329
+ /**
3330
+ * Play previous track
3331
+ */
3332
+ async previous() {
3333
+ if (this.hasPrevious) {
3334
+ return this.jump(-1);
3335
+ }
3336
+ return null;
3337
+ }
3338
+ /**
3339
+ * Shuffle tracks
3340
+ */
3341
+ shuffle(includePrevious = false) {
3342
+ if (includePrevious === true) {
3343
+ this.#tracks.push(...this.#previousTracks.splice(0));
3344
+ }
3345
+ if (this.#tracks.length < 3) {
3346
+ return this;
3347
+ }
3348
+ for (let i = this.#tracks.length - 1; i > 1; --i) {
3349
+ const j = Math.floor(Math.random() * i) + 1;
3350
+ [this.#tracks[i], this.#tracks[j]] = [this.#tracks[j], this.#tracks[i]];
3351
+ }
3352
+ return this;
3353
+ }
3354
+ /**
3355
+ * Set volume
3356
+ */
3357
+ async setVolume(volume) {
3358
+ if (!Number.isInteger(volume) || volume < 0) {
3359
+ throw this.#error("Volume must be a positive integer");
3360
+ }
3361
+ if (volume > 1e3) {
3362
+ throw this.#error("Volume cannot be more than 1000");
3363
+ }
3364
+ await this.#update({ volume });
3365
+ return this.#player.volume;
3366
+ }
3367
+ /**
3368
+ * Set autoplay
3369
+ */
3370
+ setAutoplay(autoplay = false) {
3371
+ if (typeof autoplay !== "boolean") {
3372
+ throw this.#error("Autoplay must be a boolean value");
3373
+ }
3374
+ this.#autoplay = autoplay;
3375
+ return this.#autoplay;
3376
+ }
3377
+ /**
3378
+ * Set repeat mode
3379
+ */
3380
+ setRepeatMode(repeatMode = "none") {
3381
+ if (repeatMode !== "track" && repeatMode !== "queue" && repeatMode !== "none") {
3382
+ throw this.#error("Repeat mode can only be set to track, queue, or none");
3383
+ }
3384
+ this.#repeatMode = repeatMode;
3385
+ return this.#repeatMode;
3386
+ }
3387
+ /**
3388
+ * Stop playback
3389
+ */
3390
+ async stop() {
3391
+ return this.#update({ track: { encoded: null } });
3392
+ }
3393
+ /**
3394
+ * Destroy the queue
3395
+ */
3396
+ async destroy(reason) {
3397
+ return this.player.queues.destroy(this.guildId, reason);
3398
+ }
3399
+ /**
3400
+ * Move a track from one position to another
3401
+ * @param from - Current position of the track
3402
+ * @param to - New position for the track
3403
+ */
3404
+ move(from, to) {
3405
+ if (from < 0 || from >= this.#tracks.length || to < 0 || to >= this.#tracks.length || from === to) {
3406
+ return null;
3407
+ }
3408
+ const track = this.#tracks[from];
3409
+ if (!track) {
3410
+ return null;
3411
+ }
3412
+ this.#tracks.splice(from, 1);
3413
+ this.#tracks.splice(to, 0, track);
3414
+ return track;
3415
+ }
3416
+ /**
3417
+ * Splice tracks - remove and/or add tracks at a specific position
3418
+ * @param index - Position to start
3419
+ * @param amount - Number of tracks to remove
3420
+ * @param tracks - Tracks to add at the position
3421
+ */
3422
+ splice(index, amount, tracks) {
3423
+ if (!this.#tracks.length && tracks) {
3424
+ void this.add(tracks);
3425
+ return [];
3426
+ }
3427
+ const removed = tracks ? this.#tracks.splice(index, amount, ...Array.isArray(tracks) ? tracks : [tracks]) : this.#tracks.splice(index, amount);
3428
+ return removed;
3429
+ }
3430
+ /**
3431
+ * Sort tracks by a property or custom function
3432
+ * @param sortBy - Property name or comparator function
3433
+ * @param order - Sort order (asc/desc)
3434
+ */
3435
+ sortBy(sortBy, order = "asc") {
3436
+ if (typeof sortBy === "function") {
3437
+ this.#tracks.sort(sortBy);
3438
+ } else {
3439
+ this.#tracks.sort((a, b) => {
3440
+ let comparison = 0;
3441
+ switch (sortBy) {
3442
+ case "duration":
3443
+ comparison = a.duration - b.duration;
3444
+ break;
3445
+ case "title":
3446
+ comparison = a.info.title.localeCompare(b.info.title);
3447
+ break;
3448
+ case "author":
3449
+ comparison = a.info.author.localeCompare(b.info.author);
3450
+ break;
3451
+ default:
3452
+ return 0;
3453
+ }
3454
+ return order === "desc" ? -comparison : comparison;
3455
+ });
3456
+ }
3457
+ return this;
3458
+ }
3459
+ /**
3460
+ * Get a sorted copy without modifying the original queue
3461
+ */
3462
+ toSortedBy(sortBy, order = "asc") {
3463
+ const copy = [...this.#tracks];
3464
+ if (typeof sortBy === "function") {
3465
+ return copy.sort(sortBy);
3466
+ }
3467
+ return copy.sort((a, b) => {
3468
+ let comparison = 0;
3469
+ switch (sortBy) {
3470
+ case "duration":
3471
+ comparison = a.duration - b.duration;
3472
+ break;
3473
+ case "title":
3474
+ comparison = a.info.title.localeCompare(b.info.title);
3475
+ break;
3476
+ case "author":
3477
+ comparison = a.info.author.localeCompare(b.info.author);
3478
+ break;
3479
+ default:
3480
+ return 0;
3481
+ }
3482
+ return order === "desc" ? -comparison : comparison;
3483
+ });
3484
+ }
3485
+ /**
3486
+ * Filter tracks by predicate or criteria
3487
+ */
3488
+ filterTracks(predicate) {
3489
+ if (typeof predicate === "function") {
3490
+ return this.#tracks.map((track, index) => ({ track, index })).filter(({ track, index }) => predicate(track, index));
3491
+ }
3492
+ return this.#tracks.map((track, index) => ({ track, index })).filter(({ track }) => {
3493
+ if (predicate.title && !track.info.title.toLowerCase().includes(predicate.title.toLowerCase())) {
3494
+ return false;
3495
+ }
3496
+ if (predicate.author && !track.info.author.toLowerCase().includes(predicate.author.toLowerCase())) {
3497
+ return false;
3498
+ }
3499
+ if (predicate.duration) {
3500
+ if (typeof predicate.duration === "number") {
3501
+ if (track.duration !== predicate.duration) {
3502
+ return false;
3503
+ }
3504
+ } else {
3505
+ if (predicate.duration.min && track.duration < predicate.duration.min) {
3506
+ return false;
3507
+ }
3508
+ if (predicate.duration.max && track.duration > predicate.duration.max) {
3509
+ return false;
3510
+ }
3511
+ }
3512
+ }
3513
+ if (predicate.uri && track.info.uri !== predicate.uri) {
3514
+ return false;
3515
+ }
3516
+ if (predicate.identifier && track.info.identifier !== predicate.identifier) {
3517
+ return false;
3518
+ }
3519
+ if (predicate.sourceName && track.info.sourceName !== predicate.sourceName) {
3520
+ return false;
3521
+ }
3522
+ if (predicate.isStream !== void 0 && track.isLive !== predicate.isStream) {
3523
+ return false;
3524
+ }
3525
+ if (predicate.isSeekable !== void 0 && track.isSeekable !== predicate.isSeekable) {
3526
+ return false;
3527
+ }
3528
+ return true;
3529
+ });
3530
+ }
3531
+ /**
3532
+ * Find a track by predicate or criteria
3533
+ */
3534
+ findTrack(predicate) {
3535
+ const results = this.filterTracks(predicate);
3536
+ return results[0] ?? null;
3537
+ }
3538
+ /**
3539
+ * Get a range of tracks
3540
+ */
3541
+ getTracks(start, end) {
3542
+ return this.#tracks.slice(start, end);
3543
+ }
3544
+ /**
3545
+ * Shift from previous tracks
3546
+ */
3547
+ shiftPrevious() {
3548
+ return this.#previousTracks.shift() ?? null;
3549
+ }
3550
+ };
3551
+ var QueueManager = class extends EventEmitter {
3552
+ #player;
3553
+ #queues = /* @__PURE__ */ new Map();
3554
+ #players = /* @__PURE__ */ new Map();
3555
+ constructor(player) {
3556
+ super({ captureRejections: false });
3557
+ this.#player = player;
3558
+ }
3559
+ get(guildId) {
3560
+ return this.#queues.get(guildId);
3561
+ }
3562
+ has(guildId) {
3563
+ return this.#queues.has(guildId);
3564
+ }
3565
+ get all() {
3566
+ return Array.from(this.#queues.values());
3567
+ }
3568
+ get size() {
3569
+ return this.#queues.size;
3570
+ }
3571
+ keys() {
3572
+ return this.#queues.keys();
3573
+ }
3574
+ values() {
3575
+ return this.#queues.values();
3576
+ }
3577
+ entries() {
3578
+ return this.#queues.entries();
3579
+ }
3580
+ async create(options) {
3581
+ if (this.#queues.has(options.guildId)) {
3582
+ throw new Error(`Queue already exists for guild '${options.guildId}'`);
3583
+ }
3584
+ let voice = this.#player.voices.get(options.guildId);
3585
+ if (!voice) {
3586
+ voice = await this.#player.voices.connect(options.guildId, options.voiceId, {
3587
+ node: options.node,
3588
+ context: options.context,
3589
+ filters: options.filters,
3590
+ volume: options.volume
3591
+ });
3592
+ }
3593
+ const playerState = {
3594
+ guildId: options.guildId,
3595
+ track: null,
3596
+ volume: options.volume ?? 100,
3597
+ paused: false,
3598
+ state: {
3599
+ time: Date.now(),
3600
+ position: 0,
3601
+ connected: false,
3602
+ ping: -1
3603
+ },
3604
+ voice: {
3605
+ token: "",
3606
+ endpoint: "",
3607
+ sessionId: "",
3608
+ channelId: options.voiceId
3609
+ },
3610
+ filters: options.filters ?? {}
3611
+ };
3612
+ this.#players.set(options.guildId, playerState);
3613
+ const queue = new Queue(this.#player, options.guildId, options.context);
3614
+ this.#queues.set(options.guildId, queue);
3615
+ this.#player.emit("queueCreate", queue);
3616
+ return queue;
3617
+ }
3618
+ async destroy(guildId, reason = "destroyed") {
3619
+ const queue = this.#queues.get(guildId);
3620
+ if (!queue) {
3621
+ return;
3622
+ }
3623
+ const voice = this.#player.voices.get(guildId);
3624
+ if (voice?.node) {
3625
+ await voice.node.rest.destroyPlayer(guildId).catch(noop);
3626
+ }
3627
+ await this.#player.voices.destroy(guildId, reason);
3628
+ this.#queues.delete(guildId);
3629
+ this.#players.delete(guildId);
3630
+ this.#player.emit("queueDestroy", queue, reason);
3631
+ }
3632
+ async relocate(guildId, nodeName) {
3633
+ const queue = this.#queues.get(guildId);
3634
+ if (!queue) {
3635
+ throw new Error(`No queue found for guild '${guildId}'`);
3636
+ }
3637
+ const voice = this.#player.voices.get(guildId);
3638
+ if (!voice) {
3639
+ throw new Error(`No voice connection found for guild '${guildId}'`);
3640
+ }
3641
+ await voice.changeNode(nodeName);
3642
+ }
3643
+ async syncAll() {
3644
+ const promises = [];
3645
+ for (const queue of this.#queues.values()) {
3646
+ promises.push(queue.sync("remote").catch(noop));
3647
+ }
3648
+ await Promise.all(promises);
3649
+ }
3650
+ [LookupSymbol](guildId) {
3651
+ return this.#players.get(guildId);
3652
+ }
3653
+ [OnStateUpdateSymbol](guildId, state) {
3654
+ const player = this.#players.get(guildId);
3655
+ if (!player) {
3656
+ return;
3657
+ }
3658
+ player.state = state;
3659
+ const queue = this.#queues.get(guildId);
3660
+ if (queue) {
3661
+ this.#player.emit("queueUpdate", queue, state);
3662
+ }
3663
+ const voice = this.#player.voices.get(guildId);
3664
+ if (voice?.regionId) {
3665
+ const region = this.#player.voices.regions.get(voice.regionId);
3666
+ if (region) {
3667
+ region[OnPingUpdateSymbol]?.(voice.node.name, state);
3668
+ }
3669
+ }
3670
+ }
3671
+ [OnEventUpdateSymbol](guildId, event) {
3672
+ const queue = this.#queues.get(guildId);
3673
+ if (!queue) {
3674
+ return;
3675
+ }
3676
+ switch (event.type) {
3677
+ case "TrackStartEvent" /* TrackStart */:
3678
+ this.#handleTrackStart(queue, event);
3679
+ break;
3680
+ case "TrackEndEvent" /* TrackEnd */:
3681
+ void this.#handleTrackEnd(queue, event);
3682
+ break;
3683
+ case "TrackExceptionEvent" /* TrackException */:
3684
+ this.#handleTrackException(queue, event);
3685
+ break;
3686
+ case "TrackStuckEvent" /* TrackStuck */:
3687
+ this.#handleTrackStuck(queue, event);
3688
+ break;
3689
+ case "WebSocketClosedEvent" /* WebSocketClosed */:
3690
+ this.#handleWebSocketClosed(queue, event);
3691
+ break;
3692
+ }
3693
+ }
3694
+ #handleTrackStart(queue, event) {
3695
+ const track = new Track(event.track);
3696
+ this.#player.emit("trackStart", queue, track);
3697
+ }
3698
+ async #handleTrackEnd(queue, event) {
3699
+ const track = new Track(event.track);
3700
+ const reason = event.reason;
3701
+ this.#player.emit("trackFinish", queue, track, reason);
3702
+ const shouldAdvance = reason === "finished" /* Finished */ || reason === "loadFailed" /* LoadFailed */;
3703
+ if (shouldAdvance) {
3704
+ const nextTrack = await queue.next().catch(noop);
3705
+ if (!nextTrack && queue.finished) {
3706
+ this.#player.emit("queueFinish", queue);
3707
+ }
3708
+ }
3709
+ }
3710
+ #handleTrackException(queue, event) {
3711
+ const track = new Track(event.track);
3712
+ this.#player.emit("trackError", queue, track, event.exception);
3713
+ }
3714
+ #handleTrackStuck(queue, event) {
3715
+ const track = new Track(event.track);
3716
+ this.#player.emit("trackStuck", queue, track, event.thresholdMs);
3717
+ }
3718
+ #handleWebSocketClosed(queue, event) {
3719
+ const voice = this.#player.voices.get(queue.guildId);
3720
+ if (voice) {
3721
+ this.#player.voices[OnVoiceCloseSymbol]?.(queue.guildId, event.code, event.reason, event.byRemote);
3722
+ }
3723
+ }
3724
+ };
3725
+
3726
+ // src/core/PluginSystem.ts
3727
+ var PlayerPlugin = class {
3728
+ };
3729
+
3730
+ // src/extensions/AutoplayExtension.ts
3731
+ var AutoplayPlugin = class extends PlayerPlugin {
3732
+ name = "autoplay";
3733
+ #player;
3734
+ #adding = /* @__PURE__ */ new Set();
3735
+ // Track which guilds are currently adding
3736
+ init(player) {
3737
+ this.#player = player;
3738
+ player.on("queueFinish", (queue) => {
3739
+ void this.#handleQueueFinish(queue);
3740
+ });
3741
+ }
3742
+ async #handleQueueFinish(queue) {
3743
+ const lastTrack = queue.previousTrack;
3744
+ if (!lastTrack) {
3745
+ return;
3746
+ }
3747
+ const config = queue.player.get?.(`autoplay_config_${queue.guildId}`) ?? {
3748
+ enabled: true,
3749
+ minPlayTime: 1e4,
3750
+ sources: {
3751
+ spotify: true,
3752
+ youtube: true,
3753
+ youtubemusic: true,
3754
+ soundcloud: false
3755
+ },
3756
+ limit: 5,
3757
+ minDuration: 2e4,
3758
+ maxDuration: 9e5,
3759
+ excludeKeywords: ["nightcore", "bass boosted", "8d audio", "slowed", "reverb"]
3760
+ };
3761
+ if (!config.enabled) {
3762
+ return;
3763
+ }
3764
+ if (this.#adding.has(queue.guildId)) {
3765
+ return;
3766
+ }
3767
+ this.#adding.add(queue.guildId);
3768
+ try {
3769
+ const playedData = this.#buildPlayedData(queue);
3770
+ const relatedTracks = await this.#fetchRelatedTracks(queue, lastTrack, config, playedData);
3771
+ if (relatedTracks.length > 0) {
3772
+ for (const relatedTrack of relatedTracks) {
3773
+ if (!relatedTrack.pluginInfo) {
3774
+ relatedTrack.pluginInfo = {};
3775
+ }
3776
+ relatedTrack.pluginInfo.fromAutoplay = true;
3777
+ relatedTrack.userData.requester = {
3778
+ id: this.#player.clientId ?? "autoplay",
3779
+ username: "Autoplay"
3780
+ };
3781
+ queue.add(relatedTrack);
3782
+ }
3783
+ if (queue.stopped && queue.tracks.length > 0) {
3784
+ await queue.resume();
3785
+ }
3786
+ }
3787
+ } catch (error) {
3788
+ this.#player.emit("debug", "autoplay", {
3789
+ message: `Autoplay failed: ${error.message}`,
3790
+ state: "error",
3791
+ error,
3792
+ functionLayer: "AutoplayPlugin"
3793
+ });
3794
+ } finally {
3795
+ this.#adding.delete(queue.guildId);
3796
+ }
3797
+ }
3798
+ #buildPlayedData(queue) {
3799
+ const playedIds = /* @__PURE__ */ new Set();
3800
+ const playedTracks = /* @__PURE__ */ new Set();
3801
+ const addTrack = (track) => {
3802
+ if (track.info.identifier) {
3803
+ playedIds.add(track.info.identifier);
3804
+ }
3805
+ if (track.info.isrc) {
3806
+ playedIds.add(track.info.isrc);
3807
+ }
3808
+ if (track.info.title && track.info.author) {
3809
+ const key = `${track.info.title.toLowerCase()}|${track.info.author.toLowerCase()}`;
3810
+ playedTracks.add(key);
3811
+ }
3812
+ };
3813
+ if (queue.track) {
3814
+ addTrack(queue.track);
3815
+ }
3816
+ queue.previousTracks.forEach(addTrack);
3817
+ queue.tracks.forEach(addTrack);
3818
+ return { playedIds, playedTracks };
3819
+ }
3820
+ async #fetchRelatedTracks(queue, lastTrack, config, playedData) {
3821
+ const tracks = [];
3822
+ const source = lastTrack.info.sourceName?.toLowerCase();
3823
+ if (config.sources.spotify && source?.includes("spotify")) {
3824
+ const spotifyTracks = await this.#getSpotifyRecommendations(queue, lastTrack);
3825
+ tracks.push(...spotifyTracks);
3826
+ if (tracks.length < config.limit) {
3827
+ const artistTracks = await this.#getSpotifyArtistSearch(queue, lastTrack);
3828
+ tracks.push(...artistTracks);
3829
+ }
3830
+ }
3831
+ if (tracks.length < config.limit && config.sources.youtube && source?.includes("youtube")) {
3832
+ const youtubeTracks = await this.#getYouTubeSimilar(queue, lastTrack);
3833
+ tracks.push(...youtubeTracks);
3834
+ if (tracks.length < config.limit) {
3835
+ const artistTracks = await this.#getYouTubeArtist(queue, lastTrack);
3836
+ tracks.push(...artistTracks);
3837
+ }
3838
+ }
3839
+ if (tracks.length === 0 && config.sources.youtube) {
3840
+ const youtubeTracks = await this.#getYouTubeSimilar(queue, lastTrack);
3841
+ tracks.push(...youtubeTracks);
3842
+ }
3843
+ return this.#filterAutoplayTracks(tracks, playedData, config);
3844
+ }
3845
+ #filterAutoplayTracks(tracks, playedData, config) {
3846
+ return tracks.filter((track) => {
3847
+ if (!track.info) {
3848
+ return false;
3849
+ }
3850
+ if (playedData.playedIds.has(track.info.identifier)) {
3851
+ return false;
3852
+ }
3853
+ if (track.info.isrc && playedData.playedIds.has(track.info.isrc)) {
3854
+ return false;
3855
+ }
3856
+ const key = `${track.info.title.toLowerCase()}|${track.info.author.toLowerCase()}`;
3857
+ if (playedData.playedTracks.has(key)) {
3858
+ return false;
3859
+ }
3860
+ if (track.info.length) {
3861
+ if (track.info.length < config.minDuration) {
3862
+ return false;
3863
+ }
3864
+ if (track.info.length > config.maxDuration) {
3865
+ return false;
3866
+ }
3867
+ }
3868
+ const title = track.info.title.toLowerCase();
3869
+ for (const keyword of config.excludeKeywords) {
3870
+ if (title.includes(keyword.toLowerCase())) {
3871
+ return false;
3872
+ }
3873
+ }
3874
+ return true;
3875
+ }).sort(() => Math.random() - 0.5).slice(0, config.limit);
3876
+ }
3877
+ async #getSpotifyRecommendations(queue, track) {
3878
+ try {
3879
+ const query = `sprec:seed_tracks=${track.info.identifier}`;
3880
+ const result = await queue.search(query);
3881
+ if (result.loadType === "track" /* Track */ || result.loadType === "playlist" /* Playlist */) {
3882
+ if (result.loadType === "track" /* Track */) {
3883
+ return [result.data];
3884
+ }
3885
+ return result.data.tracks;
3886
+ }
3887
+ } catch (error) {
3888
+ }
3889
+ return [];
3890
+ }
3891
+ async #getSpotifyArtistSearch(queue, track) {
3892
+ try {
3893
+ const query = `spsearch:${track.info.author}`;
3894
+ const result = await queue.search(query);
3895
+ if (result.loadType === "search" /* Search */ && Array.isArray(result.data)) {
3896
+ return result.data.slice(0, 5);
3897
+ }
3898
+ } catch (error) {
3899
+ }
3900
+ return [];
3901
+ }
3902
+ async #getYouTubeSimilar(queue, track) {
3903
+ try {
3904
+ const query = `https://www.youtube.com/watch?v=${track.info.identifier}&list=RD${track.info.identifier}`;
3905
+ const result = await queue.search(query);
3906
+ if (result.loadType === "playlist" /* Playlist */ && result.data && "tracks" in result.data) {
3907
+ return result.data.tracks.slice(0, 10);
3908
+ }
3909
+ } catch (error) {
3910
+ }
3911
+ return [];
3912
+ }
3913
+ async #getYouTubeArtist(queue, track) {
3914
+ try {
3915
+ const query = `ytsearch:${track.info.author}`;
3916
+ const result = await queue.search(query);
3917
+ if (result.loadType === "search" /* Search */ && Array.isArray(result.data)) {
3918
+ return result.data.slice(0, 5);
3919
+ }
3920
+ } catch (error) {
3921
+ }
3922
+ return [];
3923
+ }
3924
+ };
3925
+
3926
+ // src/extensions/LyricsExtension.ts
3927
+ var LyricsPlugin = class extends PlayerPlugin {
3928
+ name = "lyrics";
3929
+ #player;
3930
+ init(player) {
3931
+ this.#player = player;
3932
+ }
3933
+ /**
3934
+ * Fetch lyrics for a track
3935
+ * @param track - The track to fetch lyrics for
3936
+ * @param config - Configuration for lyrics sources
3937
+ */
3938
+ async getLyrics(track, config) {
3939
+ const defaultConfig = {
3940
+ sources: {
3941
+ lavalink: true,
3942
+ lrclib: true,
3943
+ musixmatch: false,
3944
+ genius: false
3945
+ },
3946
+ ...config
3947
+ };
3948
+ const title = track.info.title;
3949
+ const artist = track.info.author;
3950
+ if (defaultConfig.sources.lavalink) {
3951
+ const lavalinkLyrics = await this.#fetchFromLavalink(track);
3952
+ if (lavalinkLyrics) {
3953
+ this.#player.emit("lyricsFound", this.#player, track, lavalinkLyrics);
3954
+ return lavalinkLyrics;
3955
+ }
3956
+ }
3957
+ if (defaultConfig.sources.lrclib) {
3958
+ const lrclibLyrics = await this.#fetchFromLRCLib(title, artist);
3959
+ if (lrclibLyrics) {
3960
+ this.#player.emit("lyricsFound", this.#player, track, lrclibLyrics);
3961
+ return lrclibLyrics;
3962
+ }
3963
+ }
3964
+ if (defaultConfig.sources.musixmatch && defaultConfig.apiKeys?.musixmatch) {
3965
+ const musixmatchLyrics = await this.#fetchFromMusixmatch(title, artist, defaultConfig.apiKeys.musixmatch);
3966
+ if (musixmatchLyrics) {
3967
+ this.#player.emit("lyricsFound", this.#player, track, musixmatchLyrics);
3968
+ return musixmatchLyrics;
3969
+ }
3970
+ }
3971
+ if (defaultConfig.sources.genius && defaultConfig.apiKeys?.genius) {
3972
+ const geniusLyrics = await this.#fetchFromGenius(title, artist, defaultConfig.apiKeys.genius);
3973
+ if (geniusLyrics) {
3974
+ this.#player.emit("lyricsFound", this.#player, track, geniusLyrics);
3975
+ return geniusLyrics;
3976
+ }
3977
+ }
3978
+ this.#player.emit("lyricsNotFound", this.#player, track);
3979
+ return null;
3980
+ }
3981
+ /**
3982
+ * Get the current lyric line based on playback position
3983
+ */
3984
+ getCurrentLine(lyrics, position) {
3985
+ if (!lyrics.lines || lyrics.lines.length === 0) {
3986
+ return null;
3987
+ }
3988
+ for (let i = lyrics.lines.length - 1; i >= 0; i--) {
3989
+ if (lyrics.lines[i].timestamp <= position) {
3990
+ return lyrics.lines[i];
3991
+ }
3992
+ }
3993
+ return null;
3994
+ }
3995
+ /**
3996
+ * Format lyrics for display
3997
+ */
3998
+ formatLyrics(lyrics, maxLength = 2e3) {
3999
+ if (lyrics.text) {
4000
+ return lyrics.text.slice(0, maxLength);
4001
+ }
4002
+ if (lyrics.lines && lyrics.lines.length > 0) {
4003
+ let formatted = "";
4004
+ for (const line of lyrics.lines) {
4005
+ const timestamp = this.#formatTimestamp(line.timestamp);
4006
+ formatted += `[${timestamp}] ${line.line}
4007
+ `;
4008
+ if (formatted.length > maxLength) {
4009
+ return `${formatted.slice(0, maxLength)}...`;
4010
+ }
4011
+ }
4012
+ return formatted;
4013
+ }
4014
+ return "No lyrics available";
4015
+ }
4016
+ #formatTimestamp(ms) {
4017
+ const minutes = Math.floor(ms / 6e4);
4018
+ const seconds = Math.floor(ms % 6e4 / 1e3);
4019
+ return `${minutes}:${seconds.toString().padStart(2, "0")}`;
4020
+ }
4021
+ async #fetchFromLavalink(_track) {
4022
+ return await Promise.resolve(null);
4023
+ }
4024
+ async #fetchFromLRCLib(_title, _artist) {
4025
+ return await Promise.resolve(null);
4026
+ }
4027
+ async #fetchFromMusixmatch(_title, _artist, _apiKey) {
4028
+ return await Promise.resolve(null);
4029
+ }
4030
+ async #fetchFromGenius(_title, _artist, _apiToken) {
4031
+ return await Promise.resolve(null);
4032
+ }
4033
+ };
4034
+
4035
+ // src/extensions/SponsorBlockExtension.ts
4036
+ var SponsorBlockPlugin = class extends PlayerPlugin {
4037
+ name = "sponsorblock";
4038
+ #player;
4039
+ init(player) {
4040
+ this.#player = player;
4041
+ player.on("segmentsLoaded", this.#handleSegmentsLoaded.bind(this));
4042
+ player.on("segmentSkipped", this.#handleSegmentSkipped.bind(this));
4043
+ }
4044
+ /**
4045
+ * Set which segments to skip for a player
4046
+ */
4047
+ async setSegments(queue, segments = ["sponsor", "selfpromo"]) {
4048
+ const node = queue.node;
4049
+ if (!node) {
4050
+ throw new Error("No node available");
4051
+ }
4052
+ await node.setSponsorBlock(queue, segments);
4053
+ }
4054
+ /**
4055
+ * Get current SponsorBlock segments for a player
4056
+ */
4057
+ async getSegments(queue) {
4058
+ const node = queue.node;
4059
+ if (!node) {
4060
+ throw new Error("No node available");
4061
+ }
4062
+ return await node.getSponsorBlock(queue);
4063
+ }
4064
+ /**
4065
+ * Delete SponsorBlock configuration for a player
4066
+ */
4067
+ async deleteSegments(queue) {
4068
+ const node = queue.node;
4069
+ if (!node) {
4070
+ throw new Error("No node available");
4071
+ }
4072
+ await node.deleteSponsorBlock(queue);
4073
+ }
4074
+ #handleSegmentsLoaded(queue, track, segments) {
4075
+ const _segments = segments || [];
4076
+ this.#player.emit("segmentsLoaded", queue, track, _segments);
4077
+ }
4078
+ #handleSegmentSkipped(queue, track, segment) {
4079
+ const _segment = segment;
4080
+ this.#player.emit("segmentSkipped", queue, track, _segment);
4081
+ }
4082
+ };
4083
+
4084
+ // src/extensions/FairPlayExtension.ts
4085
+ var FairPlayPlugin = class extends PlayerPlugin {
4086
+ name = "fairplay";
4087
+ #player;
4088
+ config = {
4089
+ enabled: true,
4090
+ minTracks: 5,
4091
+ maxConsecutive: 3
4092
+ };
4093
+ init(player) {
4094
+ this.#player = player;
4095
+ const config = player.get?.("fairplay_config");
4096
+ if (config) {
4097
+ this.config = { ...this.config, ...config };
4098
+ }
4099
+ player.on("trackAdd", (p, g, t) => {
4100
+ void this.#handleTrackAdd(p, g, t);
4101
+ });
4102
+ }
4103
+ #handleTrackAdd(_player, guildId, _tracks) {
4104
+ if (!this.config.enabled) {
4105
+ return;
4106
+ }
4107
+ const queue = this.#player.queues.get(guildId);
4108
+ if (!queue || queue.length < this.config.minTracks) {
4109
+ return;
4110
+ }
4111
+ this.applyFairPlay(queue.guildId);
4112
+ }
4113
+ /**
4114
+ * Apply fair play algorithm to a queue
4115
+ */
4116
+ applyFairPlay(guildId) {
4117
+ const queue = this.#player.queues.get(guildId);
4118
+ if (!queue) {
4119
+ return;
4120
+ }
4121
+ const playerConfig = this.#player.get?.("fairplay_config");
4122
+ const config = playerConfig ? { ...this.config, ...playerConfig } : this.config;
4123
+ if (queue.length < config.minTracks) {
4124
+ return;
4125
+ }
4126
+ const tracks = queue.tracks;
4127
+ const fairQueue = [];
4128
+ const requesterQueues = /* @__PURE__ */ new Map();
4129
+ for (const track of tracks) {
4130
+ const requesterId = this.#getRequesterId(track);
4131
+ let requesterQueue = requesterQueues.get(requesterId);
4132
+ if (!requesterQueue) {
4133
+ requesterQueue = [];
4134
+ requesterQueues.set(requesterId, requesterQueue);
4135
+ }
4136
+ requesterQueue.push(track);
4137
+ }
4138
+ let hasMore = true;
4139
+ let consecutiveCount = 0;
4140
+ let lastRequesterId = null;
4141
+ while (hasMore) {
4142
+ hasMore = false;
4143
+ for (const [requesterId, requesterTracks] of requesterQueues) {
4144
+ if (requesterTracks.length === 0) {
4145
+ continue;
4146
+ }
4147
+ hasMore = true;
4148
+ if (lastRequesterId === requesterId) {
4149
+ consecutiveCount++;
4150
+ if (consecutiveCount >= config.maxConsecutive) {
4151
+ continue;
4152
+ }
4153
+ } else {
4154
+ consecutiveCount = 1;
4155
+ lastRequesterId = requesterId;
4156
+ }
4157
+ const track = requesterTracks.shift();
4158
+ if (track) {
4159
+ fairQueue.push(track);
4160
+ }
4161
+ }
4162
+ if (!hasMore) {
4163
+ lastRequesterId = null;
4164
+ consecutiveCount = 0;
4165
+ hasMore = Array.from(requesterQueues.values()).some((q) => q.length > 0);
4166
+ }
4167
+ }
4168
+ if (fairQueue.length > 0) {
4169
+ queue.clear("current");
4170
+ queue.add(fairQueue);
4171
+ this.#player.emit("fairPlayApplied", this.#player, guildId, fairQueue.length);
4172
+ }
4173
+ }
4174
+ #getRequesterId(track) {
4175
+ const requester = track.userData.requester;
4176
+ if (requester && typeof requester === "object") {
4177
+ return requester.id || "unknown";
4178
+ }
4179
+ return "unknown";
4180
+ }
4181
+ };
4182
+
4183
+ // src/extensions/PersistenceExtension.ts
4184
+ var MemoryQueueStore = class {
4185
+ #data = /* @__PURE__ */ new Map();
4186
+ get(guildId) {
4187
+ return this.#data.get(guildId) ?? null;
4188
+ }
4189
+ set(guildId, data) {
4190
+ this.#data.set(guildId, data);
4191
+ }
4192
+ delete(guildId) {
4193
+ this.#data.delete(guildId);
4194
+ }
4195
+ };
4196
+ var QueuePersistencePlugin = class extends PlayerPlugin {
4197
+ name = "queue-persistence";
4198
+ #player;
4199
+ #store;
4200
+ #autoSave;
4201
+ constructor(store, autoSave = true) {
4202
+ super();
4203
+ this.#store = store ?? new MemoryQueueStore();
4204
+ this.#autoSave = autoSave;
4205
+ }
4206
+ init(player) {
4207
+ this.#player = player;
4208
+ if (this.#autoSave) {
4209
+ player.on("trackStart", (queue, _track) => {
4210
+ this.saveQueue(queue.guildId).catch(() => {
4211
+ });
4212
+ });
4213
+ player.on("trackFinish", (queue, _track, _reason) => {
4214
+ this.saveQueue(queue.guildId).catch(() => {
4215
+ });
4216
+ });
4217
+ player.on("queueDestroy", (queue) => {
4218
+ this.deleteQueue(queue.guildId).catch(() => {
4219
+ });
4220
+ });
4221
+ }
4222
+ }
4223
+ /**
4224
+ * Save queue state
4225
+ */
4226
+ async saveQueue(guildId) {
4227
+ const queue = this.#player.queues.get(guildId);
4228
+ if (!queue) {
4229
+ return;
4230
+ }
4231
+ const data = {
4232
+ guildId,
4233
+ tracks: queue.tracks.map((track) => ({
4234
+ encoded: track.encoded,
4235
+ info: track.info,
4236
+ pluginInfo: track.pluginInfo,
4237
+ userData: track.userData
4238
+ })),
4239
+ previousTracks: queue.previousTracks.map((t) => ({
4240
+ encoded: t.encoded,
4241
+ info: t.info,
4242
+ pluginInfo: t.pluginInfo,
4243
+ userData: t.userData
4244
+ })),
4245
+ currentTrack: queue.track ? {
4246
+ encoded: queue.track.encoded,
4247
+ info: queue.track.info,
4248
+ position: queue.currentTime
4249
+ } : void 0,
4250
+ volume: queue.volume,
4251
+ repeatMode: queue.repeatMode,
4252
+ autoplay: queue.autoplay,
4253
+ paused: queue.paused,
4254
+ timestamp: Date.now()
4255
+ };
4256
+ await this.#store.set(guildId, data);
4257
+ this.#player.emit("queueSaved", guildId);
4258
+ }
4259
+ /**
4260
+ * Load queue state
4261
+ */
4262
+ async loadQueue(guildId) {
4263
+ const data = await this.#store.get(guildId);
4264
+ if (!data) {
4265
+ return null;
4266
+ }
4267
+ const queue = this.#player.queues.get(guildId);
4268
+ if (!queue) {
4269
+ return null;
4270
+ }
4271
+ this.#player.emit("queueLoaded", guildId, data.tracks.length);
4272
+ return data;
4273
+ }
4274
+ /**
4275
+ * Delete saved queue
4276
+ */
4277
+ async deleteQueue(guildId) {
4278
+ await this.#store.delete(guildId);
4279
+ }
4280
+ /**
4281
+ * Get all saved queues
4282
+ */
4283
+ async getAllQueues() {
4284
+ return await Promise.resolve([]);
4285
+ }
4286
+ };
4287
+ var Player = class extends EventEmitter {
4288
+ #initialized = false;
4289
+ #initPromise = null;
4290
+ #clientId = null;
4291
+ #nodes = null;
4292
+ options;
4293
+ plugins;
4294
+ nodes;
4295
+ voices;
4296
+ queues;
4297
+ constructor(options) {
4298
+ super({ captureRejections: false });
4299
+ const _options = { ...DefaultPlayerOptions, ...options };
4300
+ if (_options.nodes.length === 0) {
4301
+ throw new Error("Missing node create options");
4302
+ }
4303
+ if (typeof _options.forwardVoiceUpdate !== "function") {
4304
+ throw new Error("Missing voice update function");
4305
+ }
4306
+ this.#nodes = _options.nodes;
4307
+ delete _options.nodes;
4308
+ this.options = _options;
4309
+ this.plugins = {};
4310
+ if (_options.plugins !== void 0) {
4311
+ for (const plugin of _options.plugins) {
4312
+ if (!(plugin instanceof PlayerPlugin)) {
4313
+ throw new Error("Invalid plugin(s)");
4314
+ }
4315
+ this.plugins[plugin.name] = plugin;
4316
+ }
4317
+ delete _options.plugins;
4318
+ }
4319
+ this.nodes = new NodeManager(this);
4320
+ this.voices = new VoiceManager(this);
4321
+ this.queues = new QueueManager(this);
4322
+ const immutable = {
4323
+ writable: false,
4324
+ configurable: false
4325
+ };
4326
+ Object.defineProperties(this, {
4327
+ options: immutable,
4328
+ plugins: immutable,
4329
+ nodes: immutable,
4330
+ voices: immutable,
4331
+ queues: immutable
4332
+ });
4333
+ }
4334
+ /**
4335
+ * Whether the player is initialized and ready
4336
+ */
4337
+ get ready() {
4338
+ return this.#initialized;
4339
+ }
4340
+ /**
4341
+ * The bot's client ID
4342
+ */
4343
+ get clientId() {
4344
+ return this.#clientId;
4345
+ }
4346
+ /**
4347
+ * Initialize the player
4348
+ * @param clientId Bot client ID
4349
+ */
4350
+ async init(clientId) {
4351
+ if (this.#initialized) {
4352
+ return;
4353
+ }
4354
+ if (this.#initPromise !== null) {
4355
+ return this.#initPromise;
4356
+ }
4357
+ let resolve;
4358
+ let reject;
4359
+ const promise = new Promise((res, rej) => {
4360
+ resolve = res;
4361
+ reject = rej;
4362
+ });
4363
+ this.#initPromise = promise;
4364
+ this.#clientId = clientId;
4365
+ try {
4366
+ const nodes = this.#nodes ?? [];
4367
+ for (const node of nodes) {
4368
+ this.nodes.create({ ...node, clientId });
4369
+ }
4370
+ for (const name in this.plugins) {
4371
+ this.plugins[name].init(this);
4372
+ }
4373
+ await this.nodes.connect();
4374
+ this.#initialized = true;
4375
+ this.#nodes = null;
4376
+ this.emit("init");
4377
+ resolve();
4378
+ } catch (err) {
4379
+ reject(err);
4380
+ throw err;
4381
+ } finally {
4382
+ this.#initPromise = null;
4383
+ }
4384
+ }
4385
+ /**
4386
+ * Returns the queue of a guild
4387
+ * @param guildId Id of the guild
4388
+ */
4389
+ getQueue(guildId) {
4390
+ return this.queues.get(guildId);
4391
+ }
4392
+ /**
4393
+ * Creates a queue from options
4394
+ * @param options Options to create from
4395
+ */
4396
+ async createQueue(options) {
4397
+ return this.queues.create(options);
4398
+ }
4399
+ /**
4400
+ * Destroys the queue of a guild
4401
+ * @param guildId Id of the guild
4402
+ * @param reason Reason for destroying
4403
+ */
4404
+ async destroyQueue(guildId, reason) {
4405
+ return this.queues.destroy(guildId, reason);
4406
+ }
4407
+ /**
4408
+ * Searches for results based on query and options
4409
+ * @param query Query (or URL as well)
4410
+ * @param options Options for customization
4411
+ */
4412
+ async search(query, options) {
4413
+ if (!isString(query, "non-empty")) {
4414
+ throw new Error("Query must be a non-empty string");
4415
+ }
4416
+ const node = options?.node !== void 0 ? this.nodes.get(options.node) : this.nodes.relevant()[0];
4417
+ if (!node) {
4418
+ if (options?.node === void 0) {
4419
+ throw new Error("No nodes available");
4420
+ }
4421
+ throw new Error(`Node '${options.node}' not found`);
4422
+ }
4423
+ const prefix = options?.prefix ?? this.options.queryPrefix;
4424
+ query = isString(query, "url") ? query : `${String(prefix)}:${String(query)} `;
4425
+ const result = await node.rest.loadTracks(query);
4426
+ switch (result.loadType) {
4427
+ case "empty" /* Empty */:
4428
+ return { type: "empty", data: [] };
4429
+ case "error" /* Error */:
4430
+ return { type: "error", data: result.data };
4431
+ case "playlist" /* Playlist */:
4432
+ return { type: "playlist", data: new Playlist(result.data) };
4433
+ case "search" /* Search */:
4434
+ return { type: "query", data: result.data.map((t) => new Track(t)) };
4435
+ case "track" /* Track */:
4436
+ return { type: "track", data: new Track(result.data) };
4437
+ default:
4438
+ throw new Error(`Unexpected load result type from node '${node.name}'`);
4439
+ }
4440
+ }
4441
+ /**
4442
+ * Adds or searches if source is query and resumes the queue if stopped
4443
+ * @param source Source to play from
4444
+ * @param options Options for customization
4445
+ */
4446
+ async play(source, options) {
4447
+ let queue = this.queues.get(options.guildId);
4448
+ if (typeof source === "string") {
4449
+ let result;
4450
+ if (!queue) {
4451
+ result = await this.search(source, options);
4452
+ } else {
4453
+ result = await queue.search(source, options.prefix);
4454
+ }
4455
+ if (result.type === "empty") {
4456
+ throw new Error(`No results found for '${source}'`);
4457
+ }
4458
+ if (result.type === "error") {
4459
+ throw new Error(result.data.message ?? result.data.cause, { cause: result.data });
4460
+ }
4461
+ source = result.type === "query" ? result.data[0] : result.data;
4462
+ }
4463
+ queue ??= await this.queues.create(options);
4464
+ if (options.context !== void 0) {
4465
+ Object.assign(queue.context, options.context);
4466
+ }
4467
+ queue.add(source, options.userData);
4468
+ if (queue.stopped) {
4469
+ await queue.resume();
4470
+ }
4471
+ return queue;
4472
+ }
4473
+ /**
4474
+ * Jumps to the specified index in queue of a guild
4475
+ * @param guildId Id of the guild
4476
+ * @param index Index to jump to
4477
+ */
4478
+ async jump(guildId, index) {
4479
+ const queue = this.queues.get(guildId);
4480
+ if (!queue) {
4481
+ throw new Error(`No queue found for guild '${guildId}'`);
4482
+ }
4483
+ return queue.jump(index);
4484
+ }
4485
+ /**
4486
+ * Pauses the queue of a guild
4487
+ * @param guildId Id of the guild
4488
+ */
4489
+ async pause(guildId) {
4490
+ const queue = this.queues.get(guildId);
4491
+ if (!queue) {
4492
+ throw new Error(`No queue found for guild '${guildId}'`);
4493
+ }
4494
+ return queue.pause();
4495
+ }
4496
+ /**
4497
+ * Plays the previous track in queue of a guild
4498
+ * @param guildId Id of the guild
4499
+ */
4500
+ async previous(guildId) {
4501
+ const queue = this.queues.get(guildId);
4502
+ if (!queue) {
4503
+ throw new Error(`No queue found for guild '${guildId}'`);
4504
+ }
4505
+ return queue.previous();
4506
+ }
4507
+ /**
4508
+ * Resumes the queue of a guild
4509
+ * @param guildId Id of the guild
4510
+ */
4511
+ async resume(guildId) {
4512
+ const queue = this.queues.get(guildId);
4513
+ if (!queue) {
4514
+ throw new Error(`No queue found for guild '${guildId}'`);
4515
+ }
4516
+ return queue.resume();
4517
+ }
4518
+ /**
4519
+ * Seeks to a position in the current track of a guild
4520
+ * @param guildId Id of the guild
4521
+ * @param ms Position in milliseconds
4522
+ */
4523
+ async seek(guildId, ms) {
4524
+ const queue = this.queues.get(guildId);
4525
+ if (!queue) {
4526
+ throw new Error(`No queue found for guild '${guildId}'`);
4527
+ }
4528
+ return queue.seek(ms);
4529
+ }
4530
+ /**
4531
+ * Enables or disables autoplay for the queue of a guild
4532
+ * @param guildId Id of the guild
4533
+ * @param autoplay Whether to enable autoplay
4534
+ */
4535
+ setAutoplay(guildId, autoplay) {
4536
+ const queue = this.queues.get(guildId);
4537
+ if (!queue) {
4538
+ throw new Error(`No queue found for guild '${guildId}'`);
4539
+ }
4540
+ return queue.setAutoplay(autoplay);
4541
+ }
4542
+ /**
4543
+ * Sets the repeat mode for the queue of a guild
4544
+ * @param guildId Id of the guild
4545
+ * @param repeatMode The repeat mode
4546
+ */
4547
+ setRepeatMode(guildId, repeatMode) {
4548
+ const queue = this.queues.get(guildId);
4549
+ if (!queue) {
4550
+ throw new Error(`No queue found for guild '${guildId}'`);
4551
+ }
4552
+ return queue.setRepeatMode(repeatMode);
4553
+ }
4554
+ /**
4555
+ * Sets the volume of the queue of a guild
4556
+ * @param guildId Id of the guild
4557
+ * @param volume The volume to set
4558
+ */
4559
+ async setVolume(guildId, volume) {
4560
+ const queue = this.queues.get(guildId);
4561
+ if (!queue) {
4562
+ throw new Error(`No queue found for guild '${guildId}'`);
4563
+ }
4564
+ return queue.setVolume(volume);
4565
+ }
4566
+ /**
4567
+ * Shuffles tracks for the queue of a guild
4568
+ * @param guildId Id of the guild
4569
+ * @param includePrevious Whether to pull previous tracks to current
4570
+ */
4571
+ shuffle(guildId, includePrevious) {
4572
+ const queue = this.queues.get(guildId);
4573
+ if (!queue) {
4574
+ throw new Error(`No queue found for guild '${guildId}'`);
4575
+ }
4576
+ return queue.shuffle(includePrevious);
4577
+ }
4578
+ /**
4579
+ * Plays the next track in queue of a guild
4580
+ * @param guildId Id of the guild
4581
+ */
4582
+ async next(guildId) {
4583
+ const queue = this.queues.get(guildId);
4584
+ if (!queue) {
4585
+ throw new Error(`No queue found for guild '${guildId}'`);
4586
+ }
4587
+ return queue.next();
4588
+ }
4589
+ /**
4590
+ * Stops the queue of a guild
4591
+ * @param guildId Id of the guild
4592
+ */
4593
+ async stop(guildId) {
4594
+ const queue = this.queues.get(guildId);
4595
+ if (!queue) {
4596
+ throw new Error(`No queue found for guild '${guildId}'`);
4597
+ }
4598
+ return queue.stop();
4599
+ }
4600
+ };
4601
+
4602
+ export { AmazonMusicRegex, AnghamiRegex, AppleMusicRegex, AudioFileRegex, AudiomackRegex, AudiusRegex, AutoplayPlugin, BandcampRegex, CloseCodes, DeezerRegex, DefaultFilterOptions, DefaultNodeOptions, DefaultPlayerOptions, DefaultQueueOptions, DefaultRestOptions, EQPresets, FairPlayPlugin, FilterManager, GaanaRegex, HttpStatusCodes, InstagramRegex, JioSaavnRegex, LavalinkNode, LookupSymbol, LyricsPlugin, MemoryQueueStore, MixcloudRegex, LavalinkNode as Node, NodeManager, OnEventUpdateSymbol, OnPingUpdateSymbol, OnStateUpdateSymbol, OnVoiceCloseSymbol, PACKAGE_INFO, PandoraRegex, Player, PlayerPlugin, Playlist, QobuzRegex, Queue, QueueManager, QueuePersistencePlugin, REST, Routes, ShazamRegex, SnowflakeRegex, SoundCloudRegex, SponsorBlockPlugin, SpotifyRegex, TidalRegex, Track, TwitchRegex, UpdateSymbol, UrlRegex, VoiceManager, VoiceRegion, VoiceRegionIdRegex, VoiceState, WebSocketCloseCodes, YandexMusicRegex, YoutubeRegex, assert, chunk, clamp, formatDuration, getEQPreset, getEQPresetNames, isArray, isBoolean, isError, isFunction, isNullish, isNumber, isRecord, isSnowflake, isString, isUrl, isValidEQPreset, CLIENT_NAME as name, noop, parseDuration, randomElement, retry, shuffle, sleep, unique, validateNodeOptions, validatePlayerOptions, CLIENT_VERSION as version };
4603
+ //# sourceMappingURL=index.mjs.map
4604
+ //# sourceMappingURL=index.mjs.map