distube 5.0.2 → 5.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,2457 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/type.ts
5
+ var Events = /* @__PURE__ */ ((Events2) => {
6
+ Events2["ERROR"] = "error";
7
+ Events2["ADD_LIST"] = "addList";
8
+ Events2["ADD_SONG"] = "addSong";
9
+ Events2["PLAY_SONG"] = "playSong";
10
+ Events2["FINISH_SONG"] = "finishSong";
11
+ Events2["EMPTY"] = "empty";
12
+ Events2["FINISH"] = "finish";
13
+ Events2["INIT_QUEUE"] = "initQueue";
14
+ Events2["NO_RELATED"] = "noRelated";
15
+ Events2["DISCONNECT"] = "disconnect";
16
+ Events2["DELETE_QUEUE"] = "deleteQueue";
17
+ Events2["FFMPEG_DEBUG"] = "ffmpegDebug";
18
+ Events2["DEBUG"] = "debug";
19
+ return Events2;
20
+ })(Events || {});
21
+ var RepeatMode = /* @__PURE__ */ ((RepeatMode2) => {
22
+ RepeatMode2[RepeatMode2["DISABLED"] = 0] = "DISABLED";
23
+ RepeatMode2[RepeatMode2["SONG"] = 1] = "SONG";
24
+ RepeatMode2[RepeatMode2["QUEUE"] = 2] = "QUEUE";
25
+ return RepeatMode2;
26
+ })(RepeatMode || {});
27
+ var PluginType = /* @__PURE__ */ ((PluginType2) => {
28
+ PluginType2["EXTRACTOR"] = "extractor";
29
+ PluginType2["INFO_EXTRACTOR"] = "info-extractor";
30
+ PluginType2["PLAYABLE_EXTRACTOR"] = "playable-extractor";
31
+ return PluginType2;
32
+ })(PluginType || {});
33
+
34
+ // src/constant.ts
35
+ var defaultFilters = {
36
+ "3d": "apulsator=hz=0.125",
37
+ bassboost: "bass=g=10",
38
+ echo: "aecho=0.8:0.9:1000:0.3",
39
+ flanger: "flanger",
40
+ gate: "agate",
41
+ haas: "haas",
42
+ karaoke: "stereotools=mlev=0.1",
43
+ nightcore: "asetrate=48000*1.25,aresample=48000,bass=g=5",
44
+ reverse: "areverse",
45
+ vaporwave: "asetrate=48000*0.8,aresample=48000,atempo=1.1",
46
+ mcompand: "mcompand",
47
+ phaser: "aphaser",
48
+ tremolo: "tremolo",
49
+ surround: "surround",
50
+ earwax: "earwax"
51
+ };
52
+ var defaultOptions = {
53
+ plugins: [],
54
+ emitNewSongOnly: false,
55
+ savePreviousSongs: true,
56
+ nsfw: false,
57
+ emitAddSongWhenCreatingQueue: true,
58
+ emitAddListWhenCreatingQueue: true,
59
+ joinNewVoiceChannel: true
60
+ };
61
+
62
+ // src/struct/DisTubeError.ts
63
+ import { inspect } from "node:util";
64
+ var ERROR_MESSAGES = {
65
+ INVALID_TYPE: /* @__PURE__ */ __name((expected, got, name) => `Expected ${Array.isArray(expected) ? expected.map((e) => typeof e === "number" ? e : `'${e}'`).join(" or ") : `'${expected}'`}${name ? ` for '${name}'` : ""}, but got ${inspect(got)} (${typeof got})`, "INVALID_TYPE"),
66
+ NUMBER_COMPARE: /* @__PURE__ */ __name((name, expected, value) => `'${name}' must be ${expected} ${value}`, "NUMBER_COMPARE"),
67
+ EMPTY_ARRAY: /* @__PURE__ */ __name((name) => `'${name}' is an empty array`, "EMPTY_ARRAY"),
68
+ EMPTY_FILTERED_ARRAY: /* @__PURE__ */ __name((name, type) => `There is no valid '${type}' in the '${name}' array`, "EMPTY_FILTERED_ARRAY"),
69
+ EMPTY_STRING: /* @__PURE__ */ __name((name) => `'${name}' string must not be empty`, "EMPTY_STRING"),
70
+ INVALID_KEY: /* @__PURE__ */ __name((obj, key) => `'${key}' does not need to be provided in ${obj}`, "INVALID_KEY"),
71
+ MISSING_KEY: /* @__PURE__ */ __name((obj, key) => `'${key}' needs to be provided in ${obj}`, "MISSING_KEY"),
72
+ MISSING_KEYS: /* @__PURE__ */ __name((obj, key, all) => `${key.map((k) => `'${k}'`).join(all ? " and " : " or ")} need to be provided in ${obj}`, "MISSING_KEYS"),
73
+ MISSING_INTENTS: /* @__PURE__ */ __name((i) => `${i} intent must be provided for the Client`, "MISSING_INTENTS"),
74
+ DISABLED_OPTION: /* @__PURE__ */ __name((o) => `DisTubeOptions.${o} is disabled`, "DISABLED_OPTION"),
75
+ ENABLED_OPTION: /* @__PURE__ */ __name((o) => `DisTubeOptions.${o} is enabled`, "ENABLED_OPTION"),
76
+ NOT_IN_VOICE: "User is not in any voice channel",
77
+ VOICE_FULL: "The voice channel is full",
78
+ VOICE_ALREADY_CREATED: "This guild already has a voice connection which is not managed by DisTube",
79
+ VOICE_CONNECT_FAILED: /* @__PURE__ */ __name((s) => `Cannot connect to the voice channel after ${s} seconds`, "VOICE_CONNECT_FAILED"),
80
+ VOICE_MISSING_PERMS: "I do not have permission to join this voice channel",
81
+ VOICE_RECONNECT_FAILED: "Cannot reconnect to the voice channel",
82
+ VOICE_DIFFERENT_GUILD: "Cannot join a voice channel in a different guild",
83
+ VOICE_DIFFERENT_CLIENT: "Cannot join a voice channel created by a different client",
84
+ FFMPEG_EXITED: /* @__PURE__ */ __name((code) => `ffmpeg exited with code ${code}`, "FFMPEG_EXITED"),
85
+ FFMPEG_NOT_INSTALLED: /* @__PURE__ */ __name((path) => `ffmpeg is not installed at '${path}' path`, "FFMPEG_NOT_INSTALLED"),
86
+ ENCRYPTION_LIBRARIES_MISSING: "Cannot play audio as no valid encryption package is installed.\nPlease install sodium-native, sodium, libsodium-wrappers, or tweetnacl.",
87
+ NO_QUEUE: "There is no playing queue in this guild",
88
+ QUEUE_EXIST: "This guild has a Queue already",
89
+ QUEUE_STOPPED: "The queue has been stopped already",
90
+ PAUSED: "The queue has been paused already",
91
+ RESUMED: "The queue has been playing already",
92
+ NO_PREVIOUS: "There is no previous song in this queue",
93
+ NO_UP_NEXT: "There is no up next song",
94
+ NO_SONG_POSITION: "Does not have any song at this position",
95
+ NO_PLAYING_SONG: "There is no playing song in the queue",
96
+ NO_EXTRACTOR_PLUGIN: "There is no extractor plugin in the DisTubeOptions.plugins, please add one for searching songs",
97
+ NO_RELATED: "Cannot find any related songs",
98
+ CANNOT_PLAY_RELATED: "Cannot play the related song",
99
+ UNAVAILABLE_VIDEO: "This video is unavailable",
100
+ UNPLAYABLE_FORMATS: "No playable format found",
101
+ NON_NSFW: "Cannot play age-restricted content in non-NSFW channel",
102
+ NOT_SUPPORTED_URL: "This url is not supported",
103
+ NOT_SUPPORTED_SONG: /* @__PURE__ */ __name((song) => `There is no plugin supporting this song (${song})`, "NOT_SUPPORTED_SONG"),
104
+ NO_VALID_SONG: "'songs' array does not have any valid Song or url",
105
+ CANNOT_RESOLVE_SONG: /* @__PURE__ */ __name((t) => `Cannot resolve ${inspect(t)} to a Song`, "CANNOT_RESOLVE_SONG"),
106
+ CANNOT_GET_STREAM_URL: /* @__PURE__ */ __name((song) => `Cannot get stream url with this song (${song})`, "CANNOT_GET_STREAM_URL"),
107
+ CANNOT_GET_SEARCH_QUERY: /* @__PURE__ */ __name((song) => `Cannot get search query with this song (${song})`, "CANNOT_GET_SEARCH_QUERY"),
108
+ NO_RESULT: /* @__PURE__ */ __name((query) => `Cannot find any song with this query (${query})`, "NO_RESULT"),
109
+ NO_STREAM_URL: /* @__PURE__ */ __name((song) => `No stream url attached (${song})`, "NO_STREAM_URL"),
110
+ EMPTY_FILTERED_PLAYLIST: "There is no valid video in the playlist\nMaybe age-restricted contents is filtered because you are in non-NSFW channel",
111
+ EMPTY_PLAYLIST: "There is no valid video in the playlist"
112
+ };
113
+ var haveCode = /* @__PURE__ */ __name((code) => Object.keys(ERROR_MESSAGES).includes(code), "haveCode");
114
+ var parseMessage = /* @__PURE__ */ __name((m, ...args) => typeof m === "string" ? m : m(...args), "parseMessage");
115
+ var getErrorMessage = /* @__PURE__ */ __name((code, ...args) => haveCode(code) ? parseMessage(ERROR_MESSAGES[code], ...args) : args[0], "getErrorMessage");
116
+ var DisTubeError = class _DisTubeError extends Error {
117
+ static {
118
+ __name(this, "DisTubeError");
119
+ }
120
+ errorCode;
121
+ constructor(code, ...args) {
122
+ super(getErrorMessage(code, ...args));
123
+ this.errorCode = code;
124
+ if (Error.captureStackTrace) Error.captureStackTrace(this, _DisTubeError);
125
+ }
126
+ get name() {
127
+ return `DisTubeError [${this.errorCode}]`;
128
+ }
129
+ get code() {
130
+ return this.errorCode;
131
+ }
132
+ };
133
+
134
+ // src/struct/TaskQueue.ts
135
+ var Task = class {
136
+ static {
137
+ __name(this, "Task");
138
+ }
139
+ resolve;
140
+ promise;
141
+ constructor() {
142
+ this.promise = new Promise((res) => {
143
+ this.resolve = res;
144
+ });
145
+ }
146
+ };
147
+ var TaskQueue = class {
148
+ static {
149
+ __name(this, "TaskQueue");
150
+ }
151
+ /**
152
+ * The task array
153
+ */
154
+ #tasks = [];
155
+ /**
156
+ * Waits for last task finished and queues a new task
157
+ */
158
+ queuing() {
159
+ const next = this.remaining ? this.#tasks[this.#tasks.length - 1].promise : Promise.resolve();
160
+ this.#tasks.push(new Task());
161
+ return next;
162
+ }
163
+ /**
164
+ * Removes the finished task and processes the next task
165
+ */
166
+ resolve() {
167
+ this.#tasks.shift()?.resolve();
168
+ }
169
+ /**
170
+ * The remaining number of tasks
171
+ */
172
+ get remaining() {
173
+ return this.#tasks.length;
174
+ }
175
+ };
176
+
177
+ // src/struct/Playlist.ts
178
+ var Playlist = class {
179
+ static {
180
+ __name(this, "Playlist");
181
+ }
182
+ /**
183
+ * Playlist source.
184
+ */
185
+ source;
186
+ /**
187
+ * Songs in the playlist.
188
+ */
189
+ songs;
190
+ /**
191
+ * Playlist ID.
192
+ */
193
+ id;
194
+ /**
195
+ * Playlist name.
196
+ */
197
+ name;
198
+ /**
199
+ * Playlist URL.
200
+ */
201
+ url;
202
+ /**
203
+ * Playlist thumbnail.
204
+ */
205
+ thumbnail;
206
+ #metadata;
207
+ #member;
208
+ /**
209
+ * Create a Playlist
210
+ * @param playlist - Raw playlist info
211
+ * @param options - Optional data
212
+ */
213
+ constructor(playlist, { member, metadata } = {}) {
214
+ if (!Array.isArray(playlist.songs) || !playlist.songs.length) throw new DisTubeError("EMPTY_PLAYLIST");
215
+ this.source = playlist.source.toLowerCase();
216
+ this.songs = playlist.songs;
217
+ this.name = playlist.name;
218
+ this.id = playlist.id;
219
+ this.url = playlist.url;
220
+ this.thumbnail = playlist.thumbnail;
221
+ this.member = member;
222
+ this.songs.forEach((s) => s.playlist = this);
223
+ this.metadata = metadata;
224
+ }
225
+ /**
226
+ * Playlist duration in second.
227
+ */
228
+ get duration() {
229
+ return this.songs.reduce((prev, next) => prev + next.duration, 0);
230
+ }
231
+ /**
232
+ * Formatted duration string `hh:mm:ss`.
233
+ */
234
+ get formattedDuration() {
235
+ return formatDuration(this.duration);
236
+ }
237
+ /**
238
+ * User requested.
239
+ */
240
+ get member() {
241
+ return this.#member;
242
+ }
243
+ set member(member) {
244
+ if (!isMemberInstance(member)) return;
245
+ this.#member = member;
246
+ this.songs.forEach((s) => s.member = this.member);
247
+ }
248
+ /**
249
+ * User requested.
250
+ */
251
+ get user() {
252
+ return this.member?.user;
253
+ }
254
+ /**
255
+ * Optional metadata that can be used to identify the playlist.
256
+ */
257
+ get metadata() {
258
+ return this.#metadata;
259
+ }
260
+ set metadata(metadata) {
261
+ this.#metadata = metadata;
262
+ this.songs.forEach((s) => s.metadata = metadata);
263
+ }
264
+ toString() {
265
+ return `${this.name} (${this.songs.length} songs)`;
266
+ }
267
+ };
268
+
269
+ // src/struct/Song.ts
270
+ var Song = class {
271
+ static {
272
+ __name(this, "Song");
273
+ }
274
+ /**
275
+ * The source of this song info
276
+ */
277
+ source;
278
+ /**
279
+ * Song ID.
280
+ */
281
+ id;
282
+ /**
283
+ * Song name.
284
+ */
285
+ name;
286
+ /**
287
+ * Indicates if the song is an active live.
288
+ */
289
+ isLive;
290
+ /**
291
+ * Song duration.
292
+ */
293
+ duration;
294
+ /**
295
+ * Formatted duration string (`hh:mm:ss`, `mm:ss` or `Live`).
296
+ */
297
+ formattedDuration;
298
+ /**
299
+ * Song URL.
300
+ */
301
+ url;
302
+ /**
303
+ * Song thumbnail.
304
+ */
305
+ thumbnail;
306
+ /**
307
+ * Song view count
308
+ */
309
+ views;
310
+ /**
311
+ * Song like count
312
+ */
313
+ likes;
314
+ /**
315
+ * Song dislike count
316
+ */
317
+ dislikes;
318
+ /**
319
+ * Song repost (share) count
320
+ */
321
+ reposts;
322
+ /**
323
+ * Song uploader
324
+ */
325
+ uploader;
326
+ /**
327
+ * Whether or not an age-restricted content
328
+ */
329
+ ageRestricted;
330
+ /**
331
+ * Stream info
332
+ */
333
+ stream;
334
+ /**
335
+ * The plugin that created this song
336
+ */
337
+ plugin;
338
+ #metadata;
339
+ #member;
340
+ #playlist;
341
+ /**
342
+ * Create a Song
343
+ *
344
+ * @param info - Raw song info
345
+ * @param options - Optional data
346
+ */
347
+ constructor(info, { member, metadata } = {}) {
348
+ this.source = info.source.toLowerCase();
349
+ this.metadata = metadata;
350
+ this.member = member;
351
+ this.id = info.id;
352
+ this.name = info.name;
353
+ this.isLive = info.isLive;
354
+ this.duration = this.isLive || !info.duration ? 0 : info.duration;
355
+ this.formattedDuration = this.isLive ? "Live" : formatDuration(this.duration);
356
+ this.url = info.url;
357
+ this.thumbnail = info.thumbnail;
358
+ this.views = info.views;
359
+ this.likes = info.likes;
360
+ this.dislikes = info.dislikes;
361
+ this.reposts = info.reposts;
362
+ this.uploader = {
363
+ name: info.uploader?.name,
364
+ url: info.uploader?.url
365
+ };
366
+ this.ageRestricted = info.ageRestricted;
367
+ this.stream = { playFromSource: info.playFromSource };
368
+ this.plugin = info.plugin;
369
+ }
370
+ /**
371
+ * The playlist this song belongs to
372
+ */
373
+ get playlist() {
374
+ return this.#playlist;
375
+ }
376
+ set playlist(playlist) {
377
+ if (!(playlist instanceof Playlist)) throw new DisTubeError("INVALID_TYPE", "Playlist", playlist, "Song#playlist");
378
+ this.#playlist = playlist;
379
+ this.member = playlist.member;
380
+ }
381
+ /**
382
+ * User requested to play this song.
383
+ */
384
+ get member() {
385
+ return this.#member;
386
+ }
387
+ set member(member) {
388
+ if (isMemberInstance(member)) this.#member = member;
389
+ }
390
+ /**
391
+ * User requested to play this song.
392
+ */
393
+ get user() {
394
+ return this.member?.user;
395
+ }
396
+ /**
397
+ * Optional metadata that can be used to identify the song. This is attached by the
398
+ * {@link DisTube#play} method.
399
+ */
400
+ get metadata() {
401
+ return this.#metadata;
402
+ }
403
+ set metadata(metadata) {
404
+ this.#metadata = metadata;
405
+ }
406
+ toString() {
407
+ return this.name || this.url || this.id || "Unknown";
408
+ }
409
+ };
410
+
411
+ // src/core/DisTubeBase.ts
412
+ var DisTubeBase = class {
413
+ static {
414
+ __name(this, "DisTubeBase");
415
+ }
416
+ distube;
417
+ constructor(distube) {
418
+ this.distube = distube;
419
+ }
420
+ /**
421
+ * Emit the {@link DisTube} of this base
422
+ * @param eventName - Event name
423
+ * @param args - arguments
424
+ */
425
+ emit(eventName, ...args) {
426
+ return this.distube.emit(eventName, ...args);
427
+ }
428
+ /**
429
+ * Emit error event
430
+ * @param error - error
431
+ * @param queue - The queue encountered the error
432
+ * @param song - The playing song when encountered the error
433
+ */
434
+ emitError(error, queue, song) {
435
+ this.distube.emitError(error, queue, song);
436
+ }
437
+ /**
438
+ * Emit debug event
439
+ * @param message - debug message
440
+ */
441
+ debug(message) {
442
+ this.distube.debug(message);
443
+ }
444
+ /**
445
+ * The queue manager
446
+ */
447
+ get queues() {
448
+ return this.distube.queues;
449
+ }
450
+ /**
451
+ * The voice manager
452
+ */
453
+ get voices() {
454
+ return this.distube.voices;
455
+ }
456
+ /**
457
+ * Discord.js client
458
+ */
459
+ get client() {
460
+ return this.distube.client;
461
+ }
462
+ /**
463
+ * DisTube options
464
+ */
465
+ get options() {
466
+ return this.distube.options;
467
+ }
468
+ /**
469
+ * DisTube handler
470
+ */
471
+ get handler() {
472
+ return this.distube.handler;
473
+ }
474
+ /**
475
+ * DisTube plugins
476
+ */
477
+ get plugins() {
478
+ return this.distube.plugins;
479
+ }
480
+ };
481
+
482
+ // src/core/DisTubeVoice.ts
483
+ import { Constants } from "discord.js";
484
+ import { TypedEmitter } from "tiny-typed-emitter";
485
+ import {
486
+ AudioPlayerStatus,
487
+ VoiceConnectionDisconnectReason,
488
+ VoiceConnectionStatus,
489
+ createAudioPlayer,
490
+ entersState,
491
+ joinVoiceChannel
492
+ } from "@discordjs/voice";
493
+ var DisTubeVoice = class extends TypedEmitter {
494
+ static {
495
+ __name(this, "DisTubeVoice");
496
+ }
497
+ id;
498
+ voices;
499
+ audioPlayer;
500
+ connection;
501
+ emittedError;
502
+ isDisconnected = false;
503
+ stream;
504
+ #channel;
505
+ #volume = 100;
506
+ constructor(voiceManager, channel) {
507
+ super();
508
+ this.voices = voiceManager;
509
+ this.id = channel.guildId;
510
+ this.channel = channel;
511
+ this.voices.add(this.id, this);
512
+ this.audioPlayer = createAudioPlayer().on(AudioPlayerStatus.Idle, (oldState) => {
513
+ if (oldState.status !== AudioPlayerStatus.Idle) this.emit("finish");
514
+ }).on("error", (error) => {
515
+ if (this.emittedError) return;
516
+ this.emittedError = true;
517
+ this.emit("error", error);
518
+ });
519
+ this.connection.on(VoiceConnectionStatus.Disconnected, (_, newState) => {
520
+ if (newState.reason === VoiceConnectionDisconnectReason.Manual) {
521
+ this.leave();
522
+ } else if (newState.reason === VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
523
+ entersState(this.connection, VoiceConnectionStatus.Connecting, 5e3).catch(() => {
524
+ if (![VoiceConnectionStatus.Ready, VoiceConnectionStatus.Connecting].includes(this.connection.state.status)) {
525
+ this.leave();
526
+ }
527
+ });
528
+ } else if (this.connection.rejoinAttempts < 5) {
529
+ setTimeout(
530
+ () => {
531
+ this.connection.rejoin();
532
+ },
533
+ (this.connection.rejoinAttempts + 1) * 5e3
534
+ ).unref();
535
+ } else if (this.connection.state.status !== VoiceConnectionStatus.Destroyed) {
536
+ this.leave(new DisTubeError("VOICE_RECONNECT_FAILED"));
537
+ }
538
+ }).on(VoiceConnectionStatus.Destroyed, () => {
539
+ this.leave();
540
+ }).on("error", () => void 0);
541
+ this.connection.subscribe(this.audioPlayer);
542
+ }
543
+ /**
544
+ * The voice channel id the bot is in
545
+ */
546
+ get channelId() {
547
+ return this.connection?.joinConfig?.channelId ?? void 0;
548
+ }
549
+ get channel() {
550
+ if (!this.channelId) return this.#channel;
551
+ if (this.#channel?.id === this.channelId) return this.#channel;
552
+ const channel = this.voices.client.channels.cache.get(this.channelId);
553
+ if (!channel) return this.#channel;
554
+ for (const type of Constants.VoiceBasedChannelTypes) {
555
+ if (channel.type === type) {
556
+ this.#channel = channel;
557
+ return channel;
558
+ }
559
+ }
560
+ return this.#channel;
561
+ }
562
+ set channel(channel) {
563
+ if (!isSupportedVoiceChannel(channel)) {
564
+ throw new DisTubeError("INVALID_TYPE", "BaseGuildVoiceChannel", channel, "DisTubeVoice#channel");
565
+ }
566
+ if (channel.guildId !== this.id) throw new DisTubeError("VOICE_DIFFERENT_GUILD");
567
+ if (channel.client.user?.id !== this.voices.client.user?.id) throw new DisTubeError("VOICE_DIFFERENT_CLIENT");
568
+ if (channel.id === this.channelId) return;
569
+ if (!channel.joinable) {
570
+ if (channel.full) throw new DisTubeError("VOICE_FULL");
571
+ else throw new DisTubeError("VOICE_MISSING_PERMS");
572
+ }
573
+ this.connection = this.#join(channel);
574
+ this.#channel = channel;
575
+ }
576
+ #join(channel) {
577
+ return joinVoiceChannel({
578
+ channelId: channel.id,
579
+ guildId: this.id,
580
+ adapterCreator: channel.guild.voiceAdapterCreator,
581
+ group: channel.client.user?.id
582
+ });
583
+ }
584
+ /**
585
+ * Join a voice channel with this connection
586
+ * @param channel - A voice channel
587
+ */
588
+ async join(channel) {
589
+ const TIMEOUT = 3e4;
590
+ if (channel) this.channel = channel;
591
+ try {
592
+ await entersState(this.connection, VoiceConnectionStatus.Ready, TIMEOUT);
593
+ } catch {
594
+ if (this.connection.state.status === VoiceConnectionStatus.Ready) return this;
595
+ if (this.connection.state.status !== VoiceConnectionStatus.Destroyed) this.connection.destroy();
596
+ this.voices.remove(this.id);
597
+ throw new DisTubeError("VOICE_CONNECT_FAILED", TIMEOUT / 1e3);
598
+ }
599
+ return this;
600
+ }
601
+ /**
602
+ * Leave the voice channel of this connection
603
+ * @param error - Optional, an error to emit with 'error' event.
604
+ */
605
+ leave(error) {
606
+ this.stop(true);
607
+ if (!this.isDisconnected) {
608
+ this.emit("disconnect", error);
609
+ this.isDisconnected = true;
610
+ }
611
+ if (this.connection.state.status !== VoiceConnectionStatus.Destroyed) this.connection.destroy();
612
+ this.voices.remove(this.id);
613
+ }
614
+ /**
615
+ * Stop the playing stream
616
+ * @param force - If true, will force the {@link DisTubeVoice#audioPlayer} to enter the Idle state even
617
+ * if the {@link DisTubeStream#audioResource} has silence padding frames.
618
+ */
619
+ stop(force = false) {
620
+ this.audioPlayer.stop(force);
621
+ }
622
+ /**
623
+ * Play a {@link DisTubeStream}
624
+ * @param dtStream - DisTubeStream
625
+ */
626
+ async play(dtStream) {
627
+ if (!await checkEncryptionLibraries()) {
628
+ dtStream.kill();
629
+ throw new DisTubeError("ENCRYPTION_LIBRARIES_MISSING");
630
+ }
631
+ this.emittedError = false;
632
+ dtStream.on("error", (error) => {
633
+ if (this.emittedError || error.code === "ERR_STREAM_PREMATURE_CLOSE") return;
634
+ this.emittedError = true;
635
+ this.emit("error", error);
636
+ });
637
+ if (this.audioPlayer.state.status !== AudioPlayerStatus.Paused) this.audioPlayer.play(dtStream.audioResource);
638
+ this.stream?.kill();
639
+ this.stream = dtStream;
640
+ this.volume = this.#volume;
641
+ dtStream.spawn();
642
+ }
643
+ set volume(volume) {
644
+ if (typeof volume !== "number" || isNaN(volume)) {
645
+ throw new DisTubeError("INVALID_TYPE", "number", volume, "volume");
646
+ }
647
+ if (volume < 0) {
648
+ throw new DisTubeError("NUMBER_COMPARE", "Volume", "bigger or equal to", 0);
649
+ }
650
+ this.#volume = volume;
651
+ this.stream?.setVolume(Math.pow(this.#volume / 100, 0.5 / Math.log10(2)));
652
+ }
653
+ /**
654
+ * Get or set the volume percentage
655
+ */
656
+ get volume() {
657
+ return this.#volume;
658
+ }
659
+ /**
660
+ * Playback duration of the audio resource in seconds
661
+ */
662
+ get playbackDuration() {
663
+ return (this.stream?.audioResource?.playbackDuration ?? 0) / 1e3;
664
+ }
665
+ pause() {
666
+ this.audioPlayer.pause();
667
+ }
668
+ unpause() {
669
+ const state = this.audioPlayer.state;
670
+ if (state.status !== AudioPlayerStatus.Paused) return;
671
+ if (this.stream?.audioResource && state.resource !== this.stream.audioResource) {
672
+ this.audioPlayer.play(this.stream.audioResource);
673
+ } else {
674
+ this.audioPlayer.unpause();
675
+ }
676
+ }
677
+ /**
678
+ * Whether the bot is self-deafened
679
+ */
680
+ get selfDeaf() {
681
+ return this.connection.joinConfig.selfDeaf;
682
+ }
683
+ /**
684
+ * Whether the bot is self-muted
685
+ */
686
+ get selfMute() {
687
+ return this.connection.joinConfig.selfMute;
688
+ }
689
+ /**
690
+ * Self-deafens/undeafens the bot.
691
+ * @param selfDeaf - Whether or not the bot should be self-deafened
692
+ * @returns true if the voice state was successfully updated, otherwise false
693
+ */
694
+ setSelfDeaf(selfDeaf) {
695
+ if (typeof selfDeaf !== "boolean") {
696
+ throw new DisTubeError("INVALID_TYPE", "boolean", selfDeaf, "selfDeaf");
697
+ }
698
+ return this.connection.rejoin({
699
+ ...this.connection.joinConfig,
700
+ selfDeaf
701
+ });
702
+ }
703
+ /**
704
+ * Self-mutes/unmutes the bot.
705
+ * @param selfMute - Whether or not the bot should be self-muted
706
+ * @returns true if the voice state was successfully updated, otherwise false
707
+ */
708
+ setSelfMute(selfMute) {
709
+ if (typeof selfMute !== "boolean") {
710
+ throw new DisTubeError("INVALID_TYPE", "boolean", selfMute, "selfMute");
711
+ }
712
+ return this.connection.rejoin({
713
+ ...this.connection.joinConfig,
714
+ selfMute
715
+ });
716
+ }
717
+ /**
718
+ * The voice state of this connection
719
+ */
720
+ get voiceState() {
721
+ return this.channel?.guild?.members?.me?.voice;
722
+ }
723
+ };
724
+
725
+ // src/core/DisTubeStream.ts
726
+ import { Transform } from "stream";
727
+ import { spawn, spawnSync } from "child_process";
728
+ import { TypedEmitter as TypedEmitter2 } from "tiny-typed-emitter";
729
+ import { StreamType, createAudioResource } from "@discordjs/voice";
730
+ var checked = process.env.NODE_ENV === "test";
731
+ var checkFFmpeg = /* @__PURE__ */ __name((distube) => {
732
+ if (checked) return;
733
+ const path = distube.options.ffmpeg.path;
734
+ const debug = /* @__PURE__ */ __name((str) => distube.emit("ffmpegDebug" /* FFMPEG_DEBUG */, str), "debug");
735
+ try {
736
+ debug(`[test] spawn ffmpeg at '${path}' path`);
737
+ const process2 = spawnSync(path, ["-h"], { windowsHide: true, shell: true, encoding: "utf-8" });
738
+ if (process2.error) throw process2.error;
739
+ if (process2.stderr && !process2.stdout) throw new Error(process2.stderr);
740
+ const result = process2.output.join("\n");
741
+ const version2 = /ffmpeg version (\S+)/iu.exec(result)?.[1];
742
+ if (!version2) throw new Error("Invalid FFmpeg version");
743
+ debug(`[test] ffmpeg version: ${version2}`);
744
+ } catch (e) {
745
+ debug(`[test] failed to spawn ffmpeg at '${path}': ${e?.stack ?? e}`);
746
+ throw new DisTubeError("FFMPEG_NOT_INSTALLED", path);
747
+ }
748
+ checked = true;
749
+ }, "checkFFmpeg");
750
+ var DisTubeStream = class extends TypedEmitter2 {
751
+ static {
752
+ __name(this, "DisTubeStream");
753
+ }
754
+ #ffmpegPath;
755
+ #opts;
756
+ process;
757
+ stream;
758
+ audioResource;
759
+ /**
760
+ * Create a DisTubeStream to play with {@link DisTubeVoice}
761
+ * @param url - Stream URL
762
+ * @param options - Stream options
763
+ */
764
+ constructor(url, options) {
765
+ super();
766
+ const { ffmpeg, seek } = options;
767
+ const opts = {
768
+ reconnect: 1,
769
+ reconnect_streamed: 1,
770
+ reconnect_delay_max: 5,
771
+ analyzeduration: 0,
772
+ hide_banner: true,
773
+ ...ffmpeg.args.global,
774
+ ...ffmpeg.args.input,
775
+ i: url,
776
+ ar: 48e3,
777
+ ac: 2,
778
+ ...ffmpeg.args.output,
779
+ f: "s16le"
780
+ };
781
+ if (typeof seek === "number" && seek > 0) opts.ss = seek.toString();
782
+ const fileUrl = new URL(url);
783
+ if (fileUrl.protocol === "file:") {
784
+ opts.reconnect = null;
785
+ opts.reconnect_streamed = null;
786
+ opts.reconnect_delay_max = null;
787
+ opts.i = fileUrl.hostname + fileUrl.pathname;
788
+ }
789
+ this.#ffmpegPath = ffmpeg.path;
790
+ this.#opts = [
791
+ ...Object.entries(opts).flatMap(
792
+ ([key, value]) => Array.isArray(value) ? value.filter(Boolean).map((v) => [`-${key}`, String(v)]) : value == null || value === false ? [] : [value === true ? `-${key}` : [`-${key}`, String(value)]]
793
+ ).flat(),
794
+ "pipe:1"
795
+ ];
796
+ this.stream = new VolumeTransformer();
797
+ this.stream.on("close", () => this.kill()).on("error", (err) => {
798
+ this.debug(`[stream] error: ${err.message}`);
799
+ this.emit("error", err);
800
+ }).on("finish", () => this.debug("[stream] log: stream finished"));
801
+ this.audioResource = createAudioResource(this.stream, { inputType: StreamType.Raw, inlineVolume: false });
802
+ }
803
+ spawn() {
804
+ this.debug(`[process] spawn: ${this.#ffmpegPath} ${this.#opts.join(" ")}`);
805
+ this.process = spawn(this.#ffmpegPath, this.#opts, {
806
+ stdio: ["ignore", "pipe", "pipe"],
807
+ shell: false,
808
+ windowsHide: true
809
+ }).on("error", (err) => {
810
+ this.debug(`[process] error: ${err.message}`);
811
+ this.emit("error", err);
812
+ }).on("exit", (code, signal) => {
813
+ this.debug(`[process] exit: code=${code ?? "unknown"} signal=${signal ?? "unknown"}`);
814
+ if (!code || [0, 255].includes(code)) return;
815
+ this.debug(`[process] error: ffmpeg exited with code ${code}`);
816
+ this.emit("error", new DisTubeError("FFMPEG_EXITED", code));
817
+ });
818
+ if (!this.process.stdout || !this.process.stderr) {
819
+ this.kill();
820
+ throw new Error("Failed to create ffmpeg process");
821
+ }
822
+ this.process.stdout.pipe(this.stream);
823
+ this.process.stderr.setEncoding("utf8")?.on("data", (data) => {
824
+ const lines = data.split(/\r\n|\r|\n/u);
825
+ for (const line of lines) {
826
+ if (/^\s*$/.test(line)) continue;
827
+ this.debug(`[ffmpeg] log: ${line}`);
828
+ }
829
+ });
830
+ }
831
+ debug(debug) {
832
+ this.emit("debug", debug);
833
+ }
834
+ setVolume(volume) {
835
+ this.stream.vol = volume;
836
+ }
837
+ kill() {
838
+ if (!this.stream.destroyed) this.stream.destroy();
839
+ if (this.process && !this.process.killed) this.process.kill("SIGKILL");
840
+ }
841
+ };
842
+ var VolumeTransformer = class extends Transform {
843
+ static {
844
+ __name(this, "VolumeTransformer");
845
+ }
846
+ buffer = Buffer.allocUnsafe(0);
847
+ extrema = [-Math.pow(2, 16 - 1), Math.pow(2, 16 - 1) - 1];
848
+ vol = 1;
849
+ _transform(newChunk, _encoding, done) {
850
+ const { vol } = this;
851
+ if (vol === 1) {
852
+ this.push(newChunk);
853
+ done();
854
+ return;
855
+ }
856
+ const bytes = 2;
857
+ const chunk = Buffer.concat([this.buffer, newChunk]);
858
+ const readableLength = Math.floor(chunk.length / bytes) * bytes;
859
+ for (let i = 0; i < readableLength; i += bytes) {
860
+ const value = chunk.readInt16LE(i);
861
+ const clampedValue = Math.min(this.extrema[1], Math.max(this.extrema[0], value * vol));
862
+ chunk.writeInt16LE(clampedValue, i);
863
+ }
864
+ this.buffer = chunk.subarray(readableLength);
865
+ this.push(chunk.subarray(0, readableLength));
866
+ done();
867
+ }
868
+ };
869
+
870
+ // src/core/DisTubeHandler.ts
871
+ import { request } from "undici";
872
+ var REDIRECT_CODES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
873
+ var DisTubeHandler = class extends DisTubeBase {
874
+ static {
875
+ __name(this, "DisTubeHandler");
876
+ }
877
+ /**
878
+ * Resolve a url or a supported object to a {@link Song} or {@link Playlist}
879
+ * @throws {@link DisTubeError}
880
+ * @param input - Resolvable input
881
+ * @param options - Optional options
882
+ * @returns Resolved
883
+ */
884
+ async resolve(input, options = {}) {
885
+ if (input instanceof Song || input instanceof Playlist) {
886
+ if ("metadata" in options) input.metadata = options.metadata;
887
+ if ("member" in options) input.member = options.member;
888
+ return input;
889
+ }
890
+ if (typeof input === "string") {
891
+ if (isURL(input)) {
892
+ const plugin = await this._getPluginFromURL(input) || await this._getPluginFromURL(input = await this.followRedirectLink(input));
893
+ if (!plugin) throw new DisTubeError("NOT_SUPPORTED_URL");
894
+ this.debug(`[${plugin.constructor.name}] Resolving from url: ${input}`);
895
+ return plugin.resolve(input, options);
896
+ }
897
+ try {
898
+ const song = await this.#searchSong(input, options);
899
+ if (song) return song;
900
+ } catch {
901
+ throw new DisTubeError("NO_RESULT", input);
902
+ }
903
+ }
904
+ throw new DisTubeError("CANNOT_RESOLVE_SONG", input);
905
+ }
906
+ async _getPluginFromURL(url) {
907
+ for (const plugin of this.plugins) if (await plugin.validate(url)) return plugin;
908
+ return null;
909
+ }
910
+ async _getPluginFromSong(song, types, validate = true) {
911
+ if (!types || types.includes(song.plugin?.type)) return song.plugin;
912
+ if (!song.url) return null;
913
+ for (const plugin of this.plugins) {
914
+ if ((!types || types.includes(plugin?.type)) && (!validate || await plugin.validate(song.url))) {
915
+ return plugin;
916
+ }
917
+ }
918
+ return null;
919
+ }
920
+ async #searchSong(query, options = {}, getStreamURL = false) {
921
+ const plugins = this.plugins.filter((p) => p.type === "extractor" /* EXTRACTOR */);
922
+ if (!plugins.length) throw new DisTubeError("NO_EXTRACTOR_PLUGIN");
923
+ for (const plugin of plugins) {
924
+ this.debug(`[${plugin.constructor.name}] Searching for song: ${query}`);
925
+ const result = await plugin.searchSong(query, options);
926
+ if (result) {
927
+ if (getStreamURL && result.stream.playFromSource) result.stream.url = await plugin.getStreamURL(result);
928
+ return result;
929
+ }
930
+ }
931
+ return null;
932
+ }
933
+ /**
934
+ * Get {@link Song}'s stream info and attach it to the song.
935
+ * @param song - A Song
936
+ */
937
+ async attachStreamInfo(song) {
938
+ if (song.stream.playFromSource) {
939
+ if (song.stream.url) return;
940
+ this.debug(`[DisTubeHandler] Getting stream info: ${song}`);
941
+ const plugin = await this._getPluginFromSong(song, ["extractor" /* EXTRACTOR */, "playable-extractor" /* PLAYABLE_EXTRACTOR */]);
942
+ if (!plugin) throw new DisTubeError("NOT_SUPPORTED_SONG", song.toString());
943
+ this.debug(`[${plugin.constructor.name}] Getting stream URL: ${song}`);
944
+ song.stream.url = await plugin.getStreamURL(song);
945
+ if (!song.stream.url) throw new DisTubeError("CANNOT_GET_STREAM_URL", song.toString());
946
+ } else {
947
+ if (song.stream.song?.stream?.playFromSource && song.stream.song.stream.url) return;
948
+ this.debug(`[DisTubeHandler] Getting stream info: ${song}`);
949
+ const plugin = await this._getPluginFromSong(song, ["info-extractor" /* INFO_EXTRACTOR */]);
950
+ if (!plugin) throw new DisTubeError("NOT_SUPPORTED_SONG", song.toString());
951
+ this.debug(`[${plugin.constructor.name}] Creating search query for: ${song}`);
952
+ const query = await plugin.createSearchQuery(song);
953
+ if (!query) throw new DisTubeError("CANNOT_GET_SEARCH_QUERY", song.toString());
954
+ const altSong = await this.#searchSong(query, { metadata: song.metadata, member: song.member }, true);
955
+ if (!altSong || !altSong.stream.playFromSource) throw new DisTubeError("NO_RESULT", query || song.toString());
956
+ song.stream.song = altSong;
957
+ }
958
+ }
959
+ async followRedirectLink(url, maxRedirect = 5) {
960
+ if (maxRedirect === 0) return url;
961
+ const res = await request(url, {
962
+ method: "HEAD",
963
+ headers: {
964
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.3"
965
+ }
966
+ });
967
+ if (REDIRECT_CODES.has(res.statusCode ?? 200)) {
968
+ let location = res.headers.location;
969
+ if (typeof location !== "string") location = location?.[0] ?? url;
970
+ return this.followRedirectLink(location, --maxRedirect);
971
+ }
972
+ return url;
973
+ }
974
+ };
975
+
976
+ // src/core/DisTubeOptions.ts
977
+ var Options = class {
978
+ static {
979
+ __name(this, "Options");
980
+ }
981
+ plugins;
982
+ emitNewSongOnly;
983
+ savePreviousSongs;
984
+ customFilters;
985
+ nsfw;
986
+ emitAddSongWhenCreatingQueue;
987
+ emitAddListWhenCreatingQueue;
988
+ joinNewVoiceChannel;
989
+ ffmpeg;
990
+ constructor(options) {
991
+ if (typeof options !== "object" || Array.isArray(options)) {
992
+ throw new DisTubeError("INVALID_TYPE", "object", options, "DisTubeOptions");
993
+ }
994
+ const opts = { ...defaultOptions, ...options };
995
+ this.plugins = opts.plugins;
996
+ this.emitNewSongOnly = opts.emitNewSongOnly;
997
+ this.savePreviousSongs = opts.savePreviousSongs;
998
+ this.customFilters = opts.customFilters;
999
+ this.nsfw = opts.nsfw;
1000
+ this.emitAddSongWhenCreatingQueue = opts.emitAddSongWhenCreatingQueue;
1001
+ this.emitAddListWhenCreatingQueue = opts.emitAddListWhenCreatingQueue;
1002
+ this.joinNewVoiceChannel = opts.joinNewVoiceChannel;
1003
+ this.ffmpeg = this.#ffmpegOption(options);
1004
+ checkInvalidKey(opts, this, "DisTubeOptions");
1005
+ this.#validateOptions();
1006
+ }
1007
+ #validateOptions(options = this) {
1008
+ const booleanOptions = /* @__PURE__ */ new Set([
1009
+ "emitNewSongOnly",
1010
+ "savePreviousSongs",
1011
+ "joinNewVoiceChannel",
1012
+ "nsfw",
1013
+ "emitAddSongWhenCreatingQueue",
1014
+ "emitAddListWhenCreatingQueue"
1015
+ ]);
1016
+ const numberOptions = /* @__PURE__ */ new Set();
1017
+ const stringOptions = /* @__PURE__ */ new Set();
1018
+ const objectOptions = /* @__PURE__ */ new Set(["customFilters", "ffmpeg"]);
1019
+ const optionalOptions = /* @__PURE__ */ new Set(["customFilters"]);
1020
+ for (const [key, value] of Object.entries(options)) {
1021
+ if (value === void 0 && optionalOptions.has(key)) continue;
1022
+ if (key === "plugins" && !Array.isArray(value)) {
1023
+ throw new DisTubeError("INVALID_TYPE", "Array<Plugin>", value, `DisTubeOptions.${key}`);
1024
+ } else if (booleanOptions.has(key)) {
1025
+ if (typeof value !== "boolean") {
1026
+ throw new DisTubeError("INVALID_TYPE", "boolean", value, `DisTubeOptions.${key}`);
1027
+ }
1028
+ } else if (numberOptions.has(key)) {
1029
+ if (typeof value !== "number" || isNaN(value)) {
1030
+ throw new DisTubeError("INVALID_TYPE", "number", value, `DisTubeOptions.${key}`);
1031
+ }
1032
+ } else if (stringOptions.has(key)) {
1033
+ if (typeof value !== "string") {
1034
+ throw new DisTubeError("INVALID_TYPE", "string", value, `DisTubeOptions.${key}`);
1035
+ }
1036
+ } else if (objectOptions.has(key)) {
1037
+ if (typeof value !== "object" || Array.isArray(value)) {
1038
+ throw new DisTubeError("INVALID_TYPE", "object", value, `DisTubeOptions.${key}`);
1039
+ }
1040
+ }
1041
+ }
1042
+ }
1043
+ #ffmpegOption(opts) {
1044
+ const args = { global: {}, input: {}, output: {} };
1045
+ if (opts.ffmpeg?.args) {
1046
+ if (opts.ffmpeg.args.global) args.global = opts.ffmpeg.args.global;
1047
+ if (opts.ffmpeg.args.input) args.input = opts.ffmpeg.args.input;
1048
+ if (opts.ffmpeg.args.output) args.output = opts.ffmpeg.args.output;
1049
+ }
1050
+ const path = opts.ffmpeg?.path ?? "ffmpeg";
1051
+ if (typeof path !== "string") {
1052
+ throw new DisTubeError("INVALID_TYPE", "string", path, "DisTubeOptions.ffmpeg.path");
1053
+ }
1054
+ for (const [key, value] of Object.entries(args)) {
1055
+ if (typeof value !== "object" || Array.isArray(value)) {
1056
+ throw new DisTubeError("INVALID_TYPE", "object", value, `DisTubeOptions.ffmpeg.${key}`);
1057
+ }
1058
+ for (const [k, v] of Object.entries(value)) {
1059
+ if (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean" && !Array.isArray(v) && v !== null && v !== void 0) {
1060
+ throw new DisTubeError(
1061
+ "INVALID_TYPE",
1062
+ ["string", "number", "boolean", "Array<string | null | undefined>", "null", "undefined"],
1063
+ v,
1064
+ `DisTubeOptions.ffmpeg.${key}.${k}`
1065
+ );
1066
+ }
1067
+ }
1068
+ }
1069
+ return { path, args };
1070
+ }
1071
+ };
1072
+
1073
+ // src/core/manager/BaseManager.ts
1074
+ import { Collection } from "discord.js";
1075
+ var BaseManager = class extends DisTubeBase {
1076
+ static {
1077
+ __name(this, "BaseManager");
1078
+ }
1079
+ /**
1080
+ * The collection of items for this manager.
1081
+ */
1082
+ collection = new Collection();
1083
+ /**
1084
+ * The size of the collection.
1085
+ */
1086
+ get size() {
1087
+ return this.collection.size;
1088
+ }
1089
+ };
1090
+
1091
+ // src/core/manager/GuildIdManager.ts
1092
+ var GuildIdManager = class extends BaseManager {
1093
+ static {
1094
+ __name(this, "GuildIdManager");
1095
+ }
1096
+ add(idOrInstance, data) {
1097
+ const id = resolveGuildId(idOrInstance);
1098
+ const existing = this.get(id);
1099
+ if (existing) return this;
1100
+ this.collection.set(id, data);
1101
+ return this;
1102
+ }
1103
+ get(idOrInstance) {
1104
+ return this.collection.get(resolveGuildId(idOrInstance));
1105
+ }
1106
+ remove(idOrInstance) {
1107
+ return this.collection.delete(resolveGuildId(idOrInstance));
1108
+ }
1109
+ has(idOrInstance) {
1110
+ return this.collection.has(resolveGuildId(idOrInstance));
1111
+ }
1112
+ };
1113
+
1114
+ // src/core/manager/DisTubeVoiceManager.ts
1115
+ import { VoiceConnectionStatus as VoiceConnectionStatus2, getVoiceConnection } from "@discordjs/voice";
1116
+ var DisTubeVoiceManager = class extends GuildIdManager {
1117
+ static {
1118
+ __name(this, "DisTubeVoiceManager");
1119
+ }
1120
+ /**
1121
+ * Create a {@link DisTubeVoice} instance
1122
+ * @param channel - A voice channel to join
1123
+ */
1124
+ create(channel) {
1125
+ const existing = this.get(channel.guildId);
1126
+ if (existing) {
1127
+ existing.channel = channel;
1128
+ return existing;
1129
+ }
1130
+ if (getVoiceConnection(resolveGuildId(channel), this.client.user?.id) || getVoiceConnection(resolveGuildId(channel))) {
1131
+ throw new DisTubeError("VOICE_ALREADY_CREATED");
1132
+ }
1133
+ return new DisTubeVoice(this, channel);
1134
+ }
1135
+ /**
1136
+ * Join a voice channel and wait until the connection is ready
1137
+ * @param channel - A voice channel to join
1138
+ */
1139
+ join(channel) {
1140
+ const existing = this.get(channel.guildId);
1141
+ if (existing) return existing.join(channel);
1142
+ return this.create(channel).join();
1143
+ }
1144
+ /**
1145
+ * Leave the connected voice channel in a guild
1146
+ * @param guild - Queue Resolvable
1147
+ */
1148
+ leave(guild) {
1149
+ const voice = this.get(guild);
1150
+ if (voice) {
1151
+ voice.leave();
1152
+ } else {
1153
+ const connection = getVoiceConnection(resolveGuildId(guild), this.client.user?.id) ?? getVoiceConnection(resolveGuildId(guild));
1154
+ if (connection && connection.state.status !== VoiceConnectionStatus2.Destroyed) {
1155
+ connection.destroy();
1156
+ }
1157
+ }
1158
+ }
1159
+ };
1160
+
1161
+ // src/core/manager/FilterManager.ts
1162
+ var FilterManager = class extends BaseManager {
1163
+ static {
1164
+ __name(this, "FilterManager");
1165
+ }
1166
+ /**
1167
+ * The queue to manage
1168
+ */
1169
+ queue;
1170
+ constructor(queue) {
1171
+ super(queue.distube);
1172
+ this.queue = queue;
1173
+ }
1174
+ #resolve(filter) {
1175
+ if (typeof filter === "object" && typeof filter.name === "string" && typeof filter.value === "string") {
1176
+ return filter;
1177
+ }
1178
+ if (typeof filter === "string" && Object.prototype.hasOwnProperty.call(this.distube.filters, filter)) {
1179
+ return {
1180
+ name: filter,
1181
+ value: this.distube.filters[filter]
1182
+ };
1183
+ }
1184
+ throw new DisTubeError("INVALID_TYPE", "FilterResolvable", filter, "filter");
1185
+ }
1186
+ #apply() {
1187
+ this.queue._beginTime = this.queue.currentTime;
1188
+ this.queue.play(false);
1189
+ }
1190
+ /**
1191
+ * Enable a filter or multiple filters to the manager
1192
+ * @param filterOrFilters - The filter or filters to enable
1193
+ * @param override - Wether or not override the applied filter with new filter value
1194
+ */
1195
+ add(filterOrFilters, override = false) {
1196
+ if (Array.isArray(filterOrFilters)) {
1197
+ for (const filter of filterOrFilters) {
1198
+ const ft = this.#resolve(filter);
1199
+ if (override || !this.has(ft)) this.collection.set(ft.name, ft);
1200
+ }
1201
+ } else {
1202
+ const ft = this.#resolve(filterOrFilters);
1203
+ if (override || !this.has(ft)) this.collection.set(ft.name, ft);
1204
+ }
1205
+ this.#apply();
1206
+ return this;
1207
+ }
1208
+ /**
1209
+ * Clear enabled filters of the manager
1210
+ */
1211
+ clear() {
1212
+ return this.set([]);
1213
+ }
1214
+ /**
1215
+ * Set the filters applied to the manager
1216
+ * @param filters - The filters to apply
1217
+ */
1218
+ set(filters) {
1219
+ if (!Array.isArray(filters)) throw new DisTubeError("INVALID_TYPE", "Array<FilterResolvable>", filters, "filters");
1220
+ this.collection.clear();
1221
+ for (const f of filters) {
1222
+ const filter = this.#resolve(f);
1223
+ this.collection.set(filter.name, filter);
1224
+ }
1225
+ this.#apply();
1226
+ return this;
1227
+ }
1228
+ #removeFn(f) {
1229
+ return this.collection.delete(this.#resolve(f).name);
1230
+ }
1231
+ /**
1232
+ * Disable a filter or multiple filters
1233
+ * @param filterOrFilters - The filter or filters to disable
1234
+ */
1235
+ remove(filterOrFilters) {
1236
+ if (Array.isArray(filterOrFilters)) filterOrFilters.forEach((f) => this.#removeFn(f));
1237
+ else this.#removeFn(filterOrFilters);
1238
+ this.#apply();
1239
+ return this;
1240
+ }
1241
+ /**
1242
+ * Check whether a filter enabled or not
1243
+ * @param filter - The filter to check
1244
+ */
1245
+ has(filter) {
1246
+ return this.collection.has(typeof filter === "string" ? filter : this.#resolve(filter).name);
1247
+ }
1248
+ /**
1249
+ * Array of enabled filter names
1250
+ */
1251
+ get names() {
1252
+ return [...this.collection.keys()];
1253
+ }
1254
+ /**
1255
+ * Array of enabled filters
1256
+ */
1257
+ get values() {
1258
+ return [...this.collection.values()];
1259
+ }
1260
+ get ffmpegArgs() {
1261
+ return this.size ? { af: this.values.map((f) => f.value).join(",") } : {};
1262
+ }
1263
+ toString() {
1264
+ return this.names.toString();
1265
+ }
1266
+ };
1267
+
1268
+ // src/core/manager/QueueManager.ts
1269
+ var QueueManager = class extends GuildIdManager {
1270
+ static {
1271
+ __name(this, "QueueManager");
1272
+ }
1273
+ /**
1274
+ * Create a {@link Queue}
1275
+ * @param channel - A voice channel
1276
+ * @param textChannel - Default text channel
1277
+ * @returns Returns `true` if encounter an error
1278
+ */
1279
+ async create(channel, textChannel) {
1280
+ if (this.has(channel.guildId)) throw new DisTubeError("QUEUE_EXIST");
1281
+ this.debug(`[QueueManager] Creating queue for guild: ${channel.guildId}`);
1282
+ const voice = this.voices.create(channel);
1283
+ const queue = new Queue(this.distube, voice, textChannel);
1284
+ await queue._taskQueue.queuing();
1285
+ try {
1286
+ checkFFmpeg(this.distube);
1287
+ this.debug(`[QueueManager] Joining voice channel: ${channel.id}`);
1288
+ await voice.join();
1289
+ this.#voiceEventHandler(queue);
1290
+ this.add(queue.id, queue);
1291
+ this.emit("initQueue" /* INIT_QUEUE */, queue);
1292
+ return queue;
1293
+ } finally {
1294
+ queue._taskQueue.resolve();
1295
+ }
1296
+ }
1297
+ /**
1298
+ * Listen to DisTubeVoice events and handle the Queue
1299
+ * @param queue - Queue
1300
+ */
1301
+ #voiceEventHandler(queue) {
1302
+ queue._listeners = {
1303
+ disconnect: /* @__PURE__ */ __name((error) => {
1304
+ queue.remove();
1305
+ this.emit("disconnect" /* DISCONNECT */, queue);
1306
+ if (error) this.emitError(error, queue, queue.songs?.[0]);
1307
+ }, "disconnect"),
1308
+ error: /* @__PURE__ */ __name((error) => this.#handlePlayingError(queue, error), "error"),
1309
+ finish: /* @__PURE__ */ __name(() => this.#handleSongFinish(queue), "finish")
1310
+ };
1311
+ for (const event of objectKeys(queue._listeners)) {
1312
+ queue.voice.on(event, queue._listeners[event]);
1313
+ }
1314
+ }
1315
+ /**
1316
+ * Whether or not emit playSong event
1317
+ * @param queue - Queue
1318
+ */
1319
+ #emitPlaySong(queue) {
1320
+ if (!this.options.emitNewSongOnly) return true;
1321
+ if (queue.repeatMode === 1 /* SONG */) return queue._next || queue._prev;
1322
+ return queue.songs[0].id !== queue.songs[1].id;
1323
+ }
1324
+ /**
1325
+ * Handle the queue when a Song finish
1326
+ * @param queue - queue
1327
+ */
1328
+ async #handleSongFinish(queue) {
1329
+ this.debug(`[QueueManager] Handling song finish: ${queue.id}`);
1330
+ const song = queue.songs[0];
1331
+ this.emit("finishSong" /* FINISH_SONG */, queue, queue.songs[0]);
1332
+ await queue._taskQueue.queuing();
1333
+ try {
1334
+ if (queue.stopped) return;
1335
+ if (queue.repeatMode === 2 /* QUEUE */ && !queue._prev) queue.songs.push(song);
1336
+ if (queue._prev) {
1337
+ if (queue.repeatMode === 2 /* QUEUE */) queue.songs.unshift(queue.songs.pop());
1338
+ else queue.songs.unshift(queue.previousSongs.pop());
1339
+ }
1340
+ if (queue.songs.length <= 1 && (queue._next || queue.repeatMode === 0 /* DISABLED */)) {
1341
+ if (queue.autoplay) {
1342
+ try {
1343
+ this.debug(`[QueueManager] Adding related song: ${queue.id}`);
1344
+ await queue.addRelatedSong();
1345
+ } catch (e) {
1346
+ this.debug(`[${queue.id}] Add related song error: ${e.message}`);
1347
+ this.emit("noRelated" /* NO_RELATED */, queue, e);
1348
+ }
1349
+ }
1350
+ if (queue.songs.length <= 1) {
1351
+ this.debug(`[${queue.id}] Queue is empty, stopping...`);
1352
+ if (!queue.autoplay) this.emit("finish" /* FINISH */, queue);
1353
+ queue.remove();
1354
+ return;
1355
+ }
1356
+ }
1357
+ const emitPlaySong = this.#emitPlaySong(queue);
1358
+ if (!queue._prev && (queue.repeatMode !== 1 /* SONG */ || queue._next)) {
1359
+ const prev = queue.songs.shift();
1360
+ if (this.options.savePreviousSongs) queue.previousSongs.push(prev);
1361
+ else queue.previousSongs.push({ id: prev.id });
1362
+ }
1363
+ queue._next = queue._prev = false;
1364
+ queue._beginTime = 0;
1365
+ if (song !== queue.songs[0]) {
1366
+ const playedSong = song.stream.playFromSource ? song : song.stream.song;
1367
+ if (playedSong?.stream.playFromSource) delete playedSong.stream.url;
1368
+ }
1369
+ await this.playSong(queue, emitPlaySong);
1370
+ } finally {
1371
+ queue._taskQueue.resolve();
1372
+ }
1373
+ }
1374
+ /**
1375
+ * Handle error while playing
1376
+ * @param queue - queue
1377
+ * @param error - error
1378
+ */
1379
+ #handlePlayingError(queue, error) {
1380
+ const song = queue.songs.shift();
1381
+ try {
1382
+ error.name = "PlayingError";
1383
+ } catch {
1384
+ }
1385
+ this.debug(`[${queue.id}] Error while playing: ${error.stack || error.message}`);
1386
+ this.emitError(error, queue, song);
1387
+ if (queue.songs.length > 0) {
1388
+ this.debug(`[${queue.id}] Playing next song: ${queue.songs[0]}`);
1389
+ queue._next = queue._prev = false;
1390
+ queue._beginTime = 0;
1391
+ this.playSong(queue);
1392
+ } else {
1393
+ this.debug(`[${queue.id}] Queue is empty, stopping...`);
1394
+ queue.stop();
1395
+ }
1396
+ }
1397
+ /**
1398
+ * Play a song on voice connection with queue properties
1399
+ * @param queue - The guild queue to play
1400
+ * @param emitPlaySong - Whether or not emit {@link Events.PLAY_SONG} event
1401
+ */
1402
+ async playSong(queue, emitPlaySong = true) {
1403
+ if (!queue) return;
1404
+ if (queue.stopped || !queue.songs.length) {
1405
+ queue.stop();
1406
+ return;
1407
+ }
1408
+ try {
1409
+ const song = queue.songs[0];
1410
+ this.debug(`[${queue.id}] Getting stream from: ${song}`);
1411
+ await this.handler.attachStreamInfo(song);
1412
+ const willPlaySong = song.stream.playFromSource ? song : song.stream.song;
1413
+ const stream = willPlaySong?.stream;
1414
+ if (!willPlaySong || !stream?.playFromSource || !stream.url) throw new DisTubeError("NO_STREAM_URL", `${song}`);
1415
+ this.debug(`[${queue.id}] Creating DisTubeStream for: ${willPlaySong}`);
1416
+ const streamOptions = {
1417
+ ffmpeg: {
1418
+ path: this.options.ffmpeg.path,
1419
+ args: {
1420
+ global: { ...queue.ffmpegArgs.global },
1421
+ input: { ...queue.ffmpegArgs.input },
1422
+ output: { ...queue.ffmpegArgs.output, ...queue.filters.ffmpegArgs }
1423
+ }
1424
+ },
1425
+ seek: willPlaySong.duration ? queue._beginTime : void 0
1426
+ };
1427
+ const dtStream = new DisTubeStream(stream.url, streamOptions);
1428
+ dtStream.on("debug", (data) => this.emit("ffmpegDebug" /* FFMPEG_DEBUG */, `[${queue.id}] ${data}`));
1429
+ this.debug(`[${queue.id}] Started playing: ${willPlaySong}`);
1430
+ await queue.voice.play(dtStream);
1431
+ if (emitPlaySong) this.emit("playSong" /* PLAY_SONG */, queue, song);
1432
+ } catch (e) {
1433
+ this.#handlePlayingError(queue, e);
1434
+ }
1435
+ }
1436
+ };
1437
+
1438
+ // src/struct/Queue.ts
1439
+ var Queue = class extends DisTubeBase {
1440
+ static {
1441
+ __name(this, "Queue");
1442
+ }
1443
+ /**
1444
+ * Queue id (Guild id)
1445
+ */
1446
+ id;
1447
+ /**
1448
+ * Voice connection of this queue.
1449
+ */
1450
+ voice;
1451
+ /**
1452
+ * List of songs in the queue (The first one is the playing song)
1453
+ */
1454
+ songs;
1455
+ /**
1456
+ * List of the previous songs.
1457
+ */
1458
+ previousSongs;
1459
+ /**
1460
+ * Whether stream is currently stopped.
1461
+ */
1462
+ stopped;
1463
+ /**
1464
+ * Whether or not the stream is currently playing.
1465
+ */
1466
+ playing;
1467
+ /**
1468
+ * Whether or not the stream is currently paused.
1469
+ */
1470
+ paused;
1471
+ /**
1472
+ * Type of repeat mode (`0` is disabled, `1` is repeating a song, `2` is repeating
1473
+ * all the queue). Default value: `0` (disabled)
1474
+ */
1475
+ repeatMode;
1476
+ /**
1477
+ * Whether or not the autoplay mode is enabled. Default value: `false`
1478
+ */
1479
+ autoplay;
1480
+ /**
1481
+ * FFmpeg arguments for the current queue. Default value is defined with {@link DisTubeOptions}.ffmpeg.args.
1482
+ * `af` output argument will be replaced with {@link Queue#filters} manager
1483
+ */
1484
+ ffmpegArgs;
1485
+ /**
1486
+ * The text channel of the Queue. (Default: where the first command is called).
1487
+ */
1488
+ textChannel;
1489
+ #filters;
1490
+ /**
1491
+ * What time in the song to begin (in seconds).
1492
+ */
1493
+ _beginTime;
1494
+ /**
1495
+ * Whether or not the last song was skipped to next song.
1496
+ */
1497
+ _next;
1498
+ /**
1499
+ * Whether or not the last song was skipped to previous song.
1500
+ */
1501
+ _prev;
1502
+ /**
1503
+ * Task queuing system
1504
+ */
1505
+ _taskQueue;
1506
+ /**
1507
+ * {@link DisTubeVoice} listener
1508
+ */
1509
+ _listeners;
1510
+ /**
1511
+ * Create a queue for the guild
1512
+ * @param distube - DisTube
1513
+ * @param voice - Voice connection
1514
+ * @param textChannel - Default text channel
1515
+ */
1516
+ constructor(distube, voice, textChannel) {
1517
+ super(distube);
1518
+ this.voice = voice;
1519
+ this.id = voice.id;
1520
+ this.volume = 50;
1521
+ this.songs = [];
1522
+ this.previousSongs = [];
1523
+ this.stopped = false;
1524
+ this._next = false;
1525
+ this._prev = false;
1526
+ this.playing = false;
1527
+ this.paused = false;
1528
+ this.repeatMode = 0 /* DISABLED */;
1529
+ this.autoplay = false;
1530
+ this.#filters = new FilterManager(this);
1531
+ this._beginTime = 0;
1532
+ this.textChannel = textChannel;
1533
+ this._taskQueue = new TaskQueue();
1534
+ this._listeners = void 0;
1535
+ this.ffmpegArgs = {
1536
+ global: { ...this.options.ffmpeg.args.global },
1537
+ input: { ...this.options.ffmpeg.args.input },
1538
+ output: { ...this.options.ffmpeg.args.output }
1539
+ };
1540
+ }
1541
+ /**
1542
+ * The client user as a `GuildMember` of this queue's guild
1543
+ */
1544
+ get clientMember() {
1545
+ return this.voice.channel.guild.members.me ?? void 0;
1546
+ }
1547
+ /**
1548
+ * The filter manager of the queue
1549
+ */
1550
+ get filters() {
1551
+ return this.#filters;
1552
+ }
1553
+ /**
1554
+ * Formatted duration string.
1555
+ */
1556
+ get formattedDuration() {
1557
+ return formatDuration(this.duration);
1558
+ }
1559
+ /**
1560
+ * Queue's duration.
1561
+ */
1562
+ get duration() {
1563
+ return this.songs.length ? this.songs.reduce((prev, next) => prev + next.duration, 0) : 0;
1564
+ }
1565
+ /**
1566
+ * What time in the song is playing (in seconds).
1567
+ */
1568
+ get currentTime() {
1569
+ return this.voice.playbackDuration + this._beginTime;
1570
+ }
1571
+ /**
1572
+ * Formatted {@link Queue#currentTime} string.
1573
+ */
1574
+ get formattedCurrentTime() {
1575
+ return formatDuration(this.currentTime);
1576
+ }
1577
+ /**
1578
+ * The voice channel playing in.
1579
+ */
1580
+ get voiceChannel() {
1581
+ return this.clientMember?.voice?.channel ?? null;
1582
+ }
1583
+ /**
1584
+ * Get or set the stream volume. Default value: `50`.
1585
+ */
1586
+ get volume() {
1587
+ return this.voice.volume;
1588
+ }
1589
+ set volume(value) {
1590
+ this.voice.volume = value;
1591
+ }
1592
+ /**
1593
+ * @throws {DisTubeError}
1594
+ * @param song - Song to add
1595
+ * @param position - Position to add, \<= 0 to add to the end of the queue
1596
+ * @returns The guild queue
1597
+ */
1598
+ addToQueue(song, position = 0) {
1599
+ if (this.stopped) throw new DisTubeError("QUEUE_STOPPED");
1600
+ if (!song || Array.isArray(song) && !song.length) {
1601
+ throw new DisTubeError("INVALID_TYPE", ["Song", "Array<Song>"], song, "song");
1602
+ }
1603
+ if (typeof position !== "number" || !Number.isInteger(position)) {
1604
+ throw new DisTubeError("INVALID_TYPE", "integer", position, "position");
1605
+ }
1606
+ if (position <= 0) {
1607
+ if (Array.isArray(song)) this.songs.push(...song);
1608
+ else this.songs.push(song);
1609
+ } else if (Array.isArray(song)) {
1610
+ this.songs.splice(position, 0, ...song);
1611
+ } else {
1612
+ this.songs.splice(position, 0, song);
1613
+ }
1614
+ return this;
1615
+ }
1616
+ /**
1617
+ * @returns `true` if the queue is playing
1618
+ */
1619
+ isPlaying() {
1620
+ return this.playing;
1621
+ }
1622
+ /**
1623
+ * @returns `true` if the queue is paused
1624
+ */
1625
+ isPaused() {
1626
+ return this.paused;
1627
+ }
1628
+ /**
1629
+ * Pause the guild stream
1630
+ * @returns The guild queue
1631
+ */
1632
+ pause() {
1633
+ if (this.paused) throw new DisTubeError("PAUSED");
1634
+ this.paused = true;
1635
+ this.voice.pause();
1636
+ return this;
1637
+ }
1638
+ /**
1639
+ * Resume the guild stream
1640
+ * @returns The guild queue
1641
+ */
1642
+ resume() {
1643
+ if (!this.paused) throw new DisTubeError("RESUMED");
1644
+ this.paused = false;
1645
+ this.voice.unpause();
1646
+ return this;
1647
+ }
1648
+ /**
1649
+ * Set the guild stream's volume
1650
+ * @param percent - The percentage of volume you want to set
1651
+ * @returns The guild queue
1652
+ */
1653
+ setVolume(percent) {
1654
+ this.volume = percent;
1655
+ return this;
1656
+ }
1657
+ /**
1658
+ * Skip the playing song if there is a next song in the queue. <info>If {@link
1659
+ * Queue#autoplay} is `true` and there is no up next song, DisTube will add and
1660
+ * play a related song.</info>
1661
+ * @returns The song will skip to
1662
+ */
1663
+ async skip() {
1664
+ await this._taskQueue.queuing();
1665
+ try {
1666
+ if (this.songs.length <= 1) {
1667
+ if (this.autoplay) await this.addRelatedSong();
1668
+ else throw new DisTubeError("NO_UP_NEXT");
1669
+ }
1670
+ const song = this.songs[1];
1671
+ this._next = true;
1672
+ this.voice.stop();
1673
+ return song;
1674
+ } finally {
1675
+ this._taskQueue.resolve();
1676
+ }
1677
+ }
1678
+ /**
1679
+ * Play the previous song if exists
1680
+ * @returns The guild queue
1681
+ */
1682
+ async previous() {
1683
+ await this._taskQueue.queuing();
1684
+ try {
1685
+ if (!this.options.savePreviousSongs) throw new DisTubeError("DISABLED_OPTION", "savePreviousSongs");
1686
+ if (this.previousSongs?.length === 0 && this.repeatMode !== 2 /* QUEUE */) {
1687
+ throw new DisTubeError("NO_PREVIOUS");
1688
+ }
1689
+ const song = this.repeatMode === 2 ? this.songs[this.songs.length - 1] : this.previousSongs[this.previousSongs.length - 1];
1690
+ this._prev = true;
1691
+ this.voice.stop();
1692
+ return song;
1693
+ } finally {
1694
+ this._taskQueue.resolve();
1695
+ }
1696
+ }
1697
+ /**
1698
+ * Shuffle the queue's songs
1699
+ * @returns The guild queue
1700
+ */
1701
+ async shuffle() {
1702
+ await this._taskQueue.queuing();
1703
+ try {
1704
+ const playing = this.songs.shift();
1705
+ if (playing === void 0) return this;
1706
+ for (let i = this.songs.length - 1; i > 0; i--) {
1707
+ const j = Math.floor(Math.random() * (i + 1));
1708
+ [this.songs[i], this.songs[j]] = [this.songs[j], this.songs[i]];
1709
+ }
1710
+ this.songs.unshift(playing);
1711
+ return this;
1712
+ } finally {
1713
+ this._taskQueue.resolve();
1714
+ }
1715
+ }
1716
+ /**
1717
+ * Jump to the song position in the queue. The next one is 1, 2,... The previous
1718
+ * one is -1, -2,...
1719
+ * if `num` is invalid number
1720
+ * @param position - The song position to play
1721
+ * @returns The new Song will be played
1722
+ */
1723
+ async jump(position) {
1724
+ await this._taskQueue.queuing();
1725
+ try {
1726
+ if (typeof position !== "number") throw new DisTubeError("INVALID_TYPE", "number", position, "position");
1727
+ if (!position || position > this.songs.length || -position > this.previousSongs.length) {
1728
+ throw new DisTubeError("NO_SONG_POSITION");
1729
+ }
1730
+ let nextSong;
1731
+ if (position > 0) {
1732
+ const nextSongs = this.songs.splice(position - 1);
1733
+ if (this.options.savePreviousSongs) {
1734
+ this.previousSongs.push(...this.songs);
1735
+ } else {
1736
+ this.previousSongs.push(...this.songs.map((s) => ({ id: s.id })));
1737
+ }
1738
+ this.songs = nextSongs;
1739
+ this._next = true;
1740
+ nextSong = nextSongs[1];
1741
+ } else if (!this.options.savePreviousSongs) {
1742
+ throw new DisTubeError("DISABLED_OPTION", "savePreviousSongs");
1743
+ } else {
1744
+ this._prev = true;
1745
+ if (position !== -1) this.songs.unshift(...this.previousSongs.splice(position + 1));
1746
+ nextSong = this.previousSongs[this.previousSongs.length - 1];
1747
+ }
1748
+ this.voice.stop();
1749
+ return nextSong;
1750
+ } finally {
1751
+ this._taskQueue.resolve();
1752
+ }
1753
+ }
1754
+ /**
1755
+ * Set the repeat mode of the guild queue.
1756
+ * Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
1757
+ * @param mode - The repeat modes (toggle if `undefined`)
1758
+ * @returns The new repeat mode
1759
+ */
1760
+ setRepeatMode(mode) {
1761
+ if (mode !== void 0 && !Object.values(RepeatMode).includes(mode)) {
1762
+ throw new DisTubeError("INVALID_TYPE", ["RepeatMode", "undefined"], mode, "mode");
1763
+ }
1764
+ if (mode === void 0) this.repeatMode = (this.repeatMode + 1) % 3;
1765
+ else if (this.repeatMode === mode) this.repeatMode = 0 /* DISABLED */;
1766
+ else this.repeatMode = mode;
1767
+ return this.repeatMode;
1768
+ }
1769
+ /**
1770
+ * Set the playing time to another position
1771
+ * @param time - Time in seconds
1772
+ * @returns The guild queue
1773
+ */
1774
+ seek(time) {
1775
+ if (typeof time !== "number") throw new DisTubeError("INVALID_TYPE", "number", time, "time");
1776
+ if (isNaN(time) || time < 0) throw new DisTubeError("NUMBER_COMPARE", "time", "bigger or equal to", 0);
1777
+ this._beginTime = time;
1778
+ this.play(false);
1779
+ return this;
1780
+ }
1781
+ async #getRelatedSong(current) {
1782
+ const plugin = await this.handler._getPluginFromSong(current);
1783
+ if (plugin) return plugin.getRelatedSongs(current);
1784
+ return [];
1785
+ }
1786
+ /**
1787
+ * Add a related song of the playing song to the queue
1788
+ * @returns The added song
1789
+ */
1790
+ async addRelatedSong() {
1791
+ const current = this.songs?.[0];
1792
+ if (!current) throw new DisTubeError("NO_PLAYING_SONG");
1793
+ const prevIds = this.previousSongs.map((p) => p.id);
1794
+ const relatedSongs = (await this.#getRelatedSong(current)).filter((s) => !prevIds.includes(s.id));
1795
+ this.debug(`[${this.id}] Getting related songs from: ${current}`);
1796
+ if (!relatedSongs.length && !current.stream.playFromSource) {
1797
+ const altSong = current.stream.song;
1798
+ if (altSong) relatedSongs.push(...(await this.#getRelatedSong(altSong)).filter((s) => !prevIds.includes(s.id)));
1799
+ this.debug(`[${this.id}] Getting related songs from streamed song: ${altSong}`);
1800
+ }
1801
+ const song = relatedSongs[0];
1802
+ if (!song) throw new DisTubeError("NO_RELATED");
1803
+ song.metadata = current.metadata;
1804
+ song.member = this.clientMember;
1805
+ this.addToQueue(song);
1806
+ return song;
1807
+ }
1808
+ /**
1809
+ * Stop the guild stream and delete the queue
1810
+ */
1811
+ async stop() {
1812
+ await this._taskQueue.queuing();
1813
+ try {
1814
+ this.playing = false;
1815
+ this.paused = false;
1816
+ this.stopped = true;
1817
+ this.voice.stop();
1818
+ this.remove();
1819
+ } finally {
1820
+ this._taskQueue.resolve();
1821
+ }
1822
+ }
1823
+ /**
1824
+ * Remove the queue from the manager
1825
+ */
1826
+ remove() {
1827
+ this.stopped = true;
1828
+ this.songs = [];
1829
+ this.previousSongs = [];
1830
+ if (this._listeners) {
1831
+ for (const event of objectKeys(this._listeners)) {
1832
+ this.voice.off(event, this._listeners[event]);
1833
+ }
1834
+ }
1835
+ this.queues.remove(this.id);
1836
+ this.emit("deleteQueue" /* DELETE_QUEUE */, this);
1837
+ }
1838
+ /**
1839
+ * Toggle autoplay mode
1840
+ * @returns Autoplay mode state
1841
+ */
1842
+ toggleAutoplay() {
1843
+ this.autoplay = !this.autoplay;
1844
+ return this.autoplay;
1845
+ }
1846
+ /**
1847
+ * Play the first song in the queue
1848
+ * @param emitPlaySong - Whether or not emit {@link Events.PLAY_SONG} event
1849
+ */
1850
+ play(emitPlaySong = true) {
1851
+ if (this.stopped) throw new DisTubeError("QUEUE_STOPPED");
1852
+ this.playing = true;
1853
+ return this.queues.playSong(this, emitPlaySong);
1854
+ }
1855
+ };
1856
+
1857
+ // src/struct/Plugin.ts
1858
+ var Plugin = class {
1859
+ static {
1860
+ __name(this, "Plugin");
1861
+ }
1862
+ /**
1863
+ * DisTube
1864
+ */
1865
+ distube;
1866
+ init(distube) {
1867
+ this.distube = distube;
1868
+ }
1869
+ };
1870
+
1871
+ // src/struct/ExtractorPlugin.ts
1872
+ var ExtractorPlugin = class extends Plugin {
1873
+ static {
1874
+ __name(this, "ExtractorPlugin");
1875
+ }
1876
+ type = "extractor" /* EXTRACTOR */;
1877
+ };
1878
+
1879
+ // src/struct/InfoExtratorPlugin.ts
1880
+ var InfoExtractorPlugin = class extends Plugin {
1881
+ static {
1882
+ __name(this, "InfoExtractorPlugin");
1883
+ }
1884
+ type = "info-extractor" /* INFO_EXTRACTOR */;
1885
+ };
1886
+
1887
+ // src/struct/PlayableExtratorPlugin.ts
1888
+ var PlayableExtractorPlugin = class extends Plugin {
1889
+ static {
1890
+ __name(this, "PlayableExtractorPlugin");
1891
+ }
1892
+ type = "playable-extractor" /* PLAYABLE_EXTRACTOR */;
1893
+ };
1894
+
1895
+ // src/util.ts
1896
+ import { URL as URL2 } from "url";
1897
+ import { Constants as Constants2, GatewayIntentBits, IntentsBitField, SnowflakeUtil } from "discord.js";
1898
+ var formatInt = /* @__PURE__ */ __name((int) => int < 10 ? `0${int}` : int, "formatInt");
1899
+ function formatDuration(sec) {
1900
+ if (!sec || !Number(sec)) return "00:00";
1901
+ const seconds = Math.floor(sec % 60);
1902
+ const minutes = Math.floor(sec % 3600 / 60);
1903
+ const hours = Math.floor(sec / 3600);
1904
+ if (hours > 0) return `${formatInt(hours)}:${formatInt(minutes)}:${formatInt(seconds)}`;
1905
+ if (minutes > 0) return `${formatInt(minutes)}:${formatInt(seconds)}`;
1906
+ return `00:${formatInt(seconds)}`;
1907
+ }
1908
+ __name(formatDuration, "formatDuration");
1909
+ var SUPPORTED_PROTOCOL = ["https:", "http:", "file:"];
1910
+ function isURL(input) {
1911
+ if (typeof input !== "string" || input.includes(" ")) return false;
1912
+ try {
1913
+ const url = new URL2(input);
1914
+ if (!SUPPORTED_PROTOCOL.some((p) => p === url.protocol)) return false;
1915
+ } catch {
1916
+ return false;
1917
+ }
1918
+ return true;
1919
+ }
1920
+ __name(isURL, "isURL");
1921
+ function checkIntents(options) {
1922
+ const intents = options.intents instanceof IntentsBitField ? options.intents : new IntentsBitField(options.intents);
1923
+ if (!intents.has(GatewayIntentBits.GuildVoiceStates)) throw new DisTubeError("MISSING_INTENTS", "GuildVoiceStates");
1924
+ }
1925
+ __name(checkIntents, "checkIntents");
1926
+ function isVoiceChannelEmpty(voiceState) {
1927
+ const guild = voiceState.guild;
1928
+ const clientId = voiceState.client.user?.id;
1929
+ if (!guild || !clientId) return false;
1930
+ const voiceChannel = guild.members.me?.voice?.channel;
1931
+ if (!voiceChannel) return false;
1932
+ const members = voiceChannel.members.filter((m) => !m.user.bot);
1933
+ return !members.size;
1934
+ }
1935
+ __name(isVoiceChannelEmpty, "isVoiceChannelEmpty");
1936
+ function isSnowflake(id) {
1937
+ try {
1938
+ return SnowflakeUtil.deconstruct(id).timestamp > SnowflakeUtil.epoch;
1939
+ } catch {
1940
+ return false;
1941
+ }
1942
+ }
1943
+ __name(isSnowflake, "isSnowflake");
1944
+ function isMemberInstance(member) {
1945
+ return Boolean(member) && isSnowflake(member.id) && isSnowflake(member.guild?.id) && isSnowflake(member.user?.id) && member.id === member.user.id;
1946
+ }
1947
+ __name(isMemberInstance, "isMemberInstance");
1948
+ function isTextChannelInstance(channel) {
1949
+ return Boolean(channel) && isSnowflake(channel.id) && isSnowflake(channel.guildId || channel.guild?.id) && Constants2.TextBasedChannelTypes.includes(channel.type) && typeof channel.send === "function" && (typeof channel.nsfw === "boolean" || typeof channel.parent?.nsfw === "boolean");
1950
+ }
1951
+ __name(isTextChannelInstance, "isTextChannelInstance");
1952
+ function isMessageInstance(message) {
1953
+ return Boolean(message) && isSnowflake(message.id) && isSnowflake(message.guildId || message.guild?.id) && isMemberInstance(message.member) && isTextChannelInstance(message.channel) && Constants2.NonSystemMessageTypes.includes(message.type) && message.member.id === message.author?.id;
1954
+ }
1955
+ __name(isMessageInstance, "isMessageInstance");
1956
+ function isSupportedVoiceChannel(channel) {
1957
+ return Boolean(channel) && isSnowflake(channel.id) && isSnowflake(channel.guildId || channel.guild?.id) && Constants2.VoiceBasedChannelTypes.includes(channel.type);
1958
+ }
1959
+ __name(isSupportedVoiceChannel, "isSupportedVoiceChannel");
1960
+ function isGuildInstance(guild) {
1961
+ return Boolean(guild) && isSnowflake(guild.id) && isSnowflake(guild.ownerId) && typeof guild.name === "string";
1962
+ }
1963
+ __name(isGuildInstance, "isGuildInstance");
1964
+ function resolveGuildId(resolvable) {
1965
+ let guildId;
1966
+ if (typeof resolvable === "string") {
1967
+ guildId = resolvable;
1968
+ } else if (isObject(resolvable)) {
1969
+ if ("guildId" in resolvable && resolvable.guildId) {
1970
+ guildId = resolvable.guildId;
1971
+ } else if (resolvable instanceof Queue || resolvable instanceof DisTubeVoice || isGuildInstance(resolvable)) {
1972
+ guildId = resolvable.id;
1973
+ } else if ("guild" in resolvable && isGuildInstance(resolvable.guild)) {
1974
+ guildId = resolvable.guild.id;
1975
+ }
1976
+ }
1977
+ if (!isSnowflake(guildId)) throw new DisTubeError("INVALID_TYPE", "GuildIdResolvable", resolvable);
1978
+ return guildId;
1979
+ }
1980
+ __name(resolveGuildId, "resolveGuildId");
1981
+ function isClientInstance(client) {
1982
+ return Boolean(client) && typeof client.login === "function";
1983
+ }
1984
+ __name(isClientInstance, "isClientInstance");
1985
+ function checkInvalidKey(target, source, sourceName) {
1986
+ if (!isObject(target)) throw new DisTubeError("INVALID_TYPE", "object", target, sourceName);
1987
+ const sourceKeys = Array.isArray(source) ? source : objectKeys(source);
1988
+ const invalidKey = objectKeys(target).find((key) => !sourceKeys.includes(key));
1989
+ if (invalidKey) throw new DisTubeError("INVALID_KEY", sourceName, invalidKey);
1990
+ }
1991
+ __name(checkInvalidKey, "checkInvalidKey");
1992
+ function isObject(obj) {
1993
+ return typeof obj === "object" && obj !== null && !Array.isArray(obj);
1994
+ }
1995
+ __name(isObject, "isObject");
1996
+ function objectKeys(obj) {
1997
+ if (!isObject(obj)) return [];
1998
+ return Object.keys(obj);
1999
+ }
2000
+ __name(objectKeys, "objectKeys");
2001
+ function isNsfwChannel(channel) {
2002
+ if (!isTextChannelInstance(channel)) return false;
2003
+ if (channel.isThread()) return channel.parent?.nsfw ?? false;
2004
+ return channel.nsfw;
2005
+ }
2006
+ __name(isNsfwChannel, "isNsfwChannel");
2007
+ var isTruthy = /* @__PURE__ */ __name((x) => Boolean(x), "isTruthy");
2008
+ var checkEncryptionLibraries = /* @__PURE__ */ __name(async () => {
2009
+ for (const lib of ["sodium-native", "sodium", "libsodium-wrappers", "tweetnacl"]) {
2010
+ try {
2011
+ await import(lib);
2012
+ return true;
2013
+ } catch {
2014
+ }
2015
+ }
2016
+ return false;
2017
+ }, "checkEncryptionLibraries");
2018
+
2019
+ // src/DisTube.ts
2020
+ import { TypedEmitter as TypedEmitter3 } from "tiny-typed-emitter";
2021
+ var DisTube = class extends TypedEmitter3 {
2022
+ static {
2023
+ __name(this, "DisTube");
2024
+ }
2025
+ /**
2026
+ * @event
2027
+ * Emitted after DisTube add a new playlist to the playing {@link Queue}.
2028
+ * @param queue - The guild queue
2029
+ * @param playlist - Playlist info
2030
+ */
2031
+ static ["addList" /* ADD_LIST */];
2032
+ /**
2033
+ * @event
2034
+ * Emitted after DisTube add a new song to the playing {@link Queue}.
2035
+ * @param queue - The guild queue
2036
+ * @param song - Added song
2037
+ */
2038
+ static ["addSong" /* ADD_SONG */];
2039
+ /**
2040
+ * @event
2041
+ * Emitted when a {@link Queue} is deleted with any reasons.
2042
+ * @param queue - The guild queue
2043
+ */
2044
+ static ["deleteQueue" /* DELETE_QUEUE */];
2045
+ /**
2046
+ * @event
2047
+ * Emitted when the bot is disconnected to a voice channel.
2048
+ * @param queue - The guild queue
2049
+ */
2050
+ static ["disconnect" /* DISCONNECT */];
2051
+ /**
2052
+ * @event
2053
+ * Emitted when DisTube encounters an error while playing songs.
2054
+ * @param error - error
2055
+ * @param queue - The queue encountered the error
2056
+ * @param song - The playing song when encountered the error
2057
+ */
2058
+ static ["error" /* ERROR */];
2059
+ /**
2060
+ * @event
2061
+ * Emitted for logging FFmpeg debug information.
2062
+ * @param debug - Debug message string.
2063
+ */
2064
+ static ["ffmpegDebug" /* FFMPEG_DEBUG */];
2065
+ /**
2066
+ * @event
2067
+ * Emitted to provide debug information from DisTube's operation.
2068
+ * Useful for troubleshooting or logging purposes.
2069
+ *
2070
+ * @param debug - Debug message string.
2071
+ */
2072
+ static ["debug" /* DEBUG */];
2073
+ /**
2074
+ * @event
2075
+ * Emitted when there is no more song in the queue and {@link Queue#autoplay} is `false`.
2076
+ * @param queue - The guild queue
2077
+ */
2078
+ static ["finish" /* FINISH */];
2079
+ /**
2080
+ * @event
2081
+ * Emitted when DisTube finished a song.
2082
+ * @param queue - The guild queue
2083
+ * @param song - Finished song
2084
+ */
2085
+ static ["finishSong" /* FINISH_SONG */];
2086
+ /**
2087
+ * @event
2088
+ * Emitted when DisTube initialize a queue to change queue default properties.
2089
+ * @param queue - The guild queue
2090
+ */
2091
+ static ["initQueue" /* INIT_QUEUE */];
2092
+ /**
2093
+ * @event
2094
+ * Emitted when {@link Queue#autoplay} is `true`, {@link Queue#songs} is empty, and
2095
+ * DisTube cannot find related songs to play.
2096
+ * @param queue - The guild queue
2097
+ */
2098
+ static ["noRelated" /* NO_RELATED */];
2099
+ /**
2100
+ * @event
2101
+ * Emitted when DisTube play a song.
2102
+ * If {@link DisTubeOptions}.emitNewSongOnly is `true`, this event is not emitted
2103
+ * when looping a song or next song is the previous one.
2104
+ * @param queue - The guild queue
2105
+ * @param song - Playing song
2106
+ */
2107
+ static ["playSong" /* PLAY_SONG */];
2108
+ /**
2109
+ * DisTube internal handler
2110
+ */
2111
+ handler;
2112
+ /**
2113
+ * DisTube options
2114
+ */
2115
+ options;
2116
+ /**
2117
+ * Discord.js v14 client
2118
+ */
2119
+ client;
2120
+ /**
2121
+ * Queues manager
2122
+ */
2123
+ queues;
2124
+ /**
2125
+ * DisTube voice connections manager
2126
+ */
2127
+ voices;
2128
+ /**
2129
+ * DisTube plugins
2130
+ */
2131
+ plugins;
2132
+ /**
2133
+ * DisTube ffmpeg audio filters
2134
+ */
2135
+ filters;
2136
+ /**
2137
+ * Create a new DisTube class.
2138
+ * @throws {@link DisTubeError}
2139
+ * @param client - Discord.JS client
2140
+ * @param opts - Custom DisTube options
2141
+ */
2142
+ constructor(client, opts = {}) {
2143
+ super();
2144
+ this.setMaxListeners(1);
2145
+ if (!isClientInstance(client)) throw new DisTubeError("INVALID_TYPE", "Discord.Client", client, "client");
2146
+ this.client = client;
2147
+ checkIntents(client.options);
2148
+ this.options = new Options(opts);
2149
+ this.voices = new DisTubeVoiceManager(this);
2150
+ this.handler = new DisTubeHandler(this);
2151
+ this.queues = new QueueManager(this);
2152
+ this.filters = { ...defaultFilters, ...this.options.customFilters };
2153
+ this.plugins = [...this.options.plugins];
2154
+ this.plugins.forEach((p) => p.init(this));
2155
+ }
2156
+ static get version() {
2157
+ return version;
2158
+ }
2159
+ /**
2160
+ * DisTube version
2161
+ */
2162
+ get version() {
2163
+ return version;
2164
+ }
2165
+ /**
2166
+ * Play / add a song or playlist from url.
2167
+ * Search and play a song (with {@link ExtractorPlugin}) if it is not a valid url.
2168
+ * @throws {@link DisTubeError}
2169
+ * @param voiceChannel - The channel will be joined if the bot isn't in any channels, the bot will be
2170
+ * moved to this channel if {@link DisTubeOptions}.joinNewVoiceChannel is `true`
2171
+ * @param song - URL | Search string | {@link Song} | {@link Playlist}
2172
+ * @param options - Optional options
2173
+ */
2174
+ async play(voiceChannel, song, options = {}) {
2175
+ if (!isSupportedVoiceChannel(voiceChannel)) {
2176
+ throw new DisTubeError("INVALID_TYPE", "BaseGuildVoiceChannel", voiceChannel, "voiceChannel");
2177
+ }
2178
+ if (!isObject(options)) throw new DisTubeError("INVALID_TYPE", "object", options, "options");
2179
+ const { textChannel, member, skip, message, metadata } = {
2180
+ member: voiceChannel.guild.members.me ?? void 0,
2181
+ textChannel: options?.message?.channel,
2182
+ skip: false,
2183
+ ...options
2184
+ };
2185
+ const position = Number(options.position) || (skip ? 1 : 0);
2186
+ if (message && !isMessageInstance(message)) {
2187
+ throw new DisTubeError("INVALID_TYPE", ["Discord.Message", "a falsy value"], message, "options.message");
2188
+ }
2189
+ if (textChannel && !isTextChannelInstance(textChannel)) {
2190
+ throw new DisTubeError("INVALID_TYPE", "Discord.GuildTextBasedChannel", textChannel, "options.textChannel");
2191
+ }
2192
+ if (member && !isMemberInstance(member)) {
2193
+ throw new DisTubeError("INVALID_TYPE", "Discord.GuildMember", member, "options.member");
2194
+ }
2195
+ const queue = this.getQueue(voiceChannel) || await this.queues.create(voiceChannel, textChannel);
2196
+ await queue._taskQueue.queuing();
2197
+ try {
2198
+ this.debug(`[${queue.id}] Playing input: ${song}`);
2199
+ const resolved = await this.handler.resolve(song, { member, metadata });
2200
+ const isNsfw = isNsfwChannel(queue?.textChannel || textChannel);
2201
+ if (resolved instanceof Playlist) {
2202
+ if (!this.options.nsfw && !isNsfw) {
2203
+ resolved.songs = resolved.songs.filter((s) => !s.ageRestricted);
2204
+ if (!resolved.songs.length) throw new DisTubeError("EMPTY_FILTERED_PLAYLIST");
2205
+ }
2206
+ if (!resolved.songs.length) throw new DisTubeError("EMPTY_PLAYLIST");
2207
+ this.debug(`[${queue.id}] Adding playlist to queue: ${resolved.songs.length} songs`);
2208
+ queue.addToQueue(resolved.songs, position);
2209
+ if (queue.playing || this.options.emitAddListWhenCreatingQueue) this.emit("addList" /* ADD_LIST */, queue, resolved);
2210
+ } else {
2211
+ if (!this.options.nsfw && resolved.ageRestricted && !isNsfwChannel(queue?.textChannel || textChannel)) {
2212
+ throw new DisTubeError("NON_NSFW");
2213
+ }
2214
+ this.debug(`[${queue.id}] Adding song to queue: ${resolved.name || resolved.url || resolved.id || resolved}`);
2215
+ queue.addToQueue(resolved, position);
2216
+ if (queue.playing || this.options.emitAddSongWhenCreatingQueue) this.emit("addSong" /* ADD_SONG */, queue, resolved);
2217
+ }
2218
+ if (!queue.playing) await queue.play();
2219
+ else if (skip) await queue.skip();
2220
+ } catch (e) {
2221
+ if (!(e instanceof DisTubeError)) {
2222
+ this.debug(`[${queue.id}] Unexpected error while playing song: ${e.stack || e.message}`);
2223
+ try {
2224
+ e.name = "PlayError";
2225
+ e.message = `${typeof song === "string" ? song : song.url}
2226
+ ${e.message}`;
2227
+ } catch {
2228
+ }
2229
+ }
2230
+ throw e;
2231
+ } finally {
2232
+ queue._taskQueue.resolve();
2233
+ }
2234
+ }
2235
+ /**
2236
+ * Create a custom playlist
2237
+ * @param songs - Array of url or Song
2238
+ * @param options - Optional options
2239
+ */
2240
+ async createCustomPlaylist(songs, { member, parallel, metadata, name, source, url, thumbnail } = {}) {
2241
+ if (!Array.isArray(songs)) throw new DisTubeError("INVALID_TYPE", "Array", songs, "songs");
2242
+ if (!songs.length) throw new DisTubeError("EMPTY_ARRAY", "songs");
2243
+ const filteredSongs = songs.filter((song) => song instanceof Song || isURL(song));
2244
+ if (!filteredSongs.length) throw new DisTubeError("NO_VALID_SONG");
2245
+ if (member && !isMemberInstance(member)) {
2246
+ throw new DisTubeError("INVALID_TYPE", "Discord.Member", member, "options.member");
2247
+ }
2248
+ let resolvedSongs;
2249
+ if (parallel !== false) {
2250
+ const promises = filteredSongs.map(
2251
+ (song) => this.handler.resolve(song, { member, metadata }).catch(() => void 0)
2252
+ );
2253
+ resolvedSongs = (await Promise.all(promises)).filter((s) => s instanceof Song);
2254
+ } else {
2255
+ resolvedSongs = [];
2256
+ for (const song of filteredSongs) {
2257
+ const resolved = await this.handler.resolve(song, { member, metadata }).catch(() => void 0);
2258
+ if (resolved instanceof Song) resolvedSongs.push(resolved);
2259
+ }
2260
+ }
2261
+ return new Playlist(
2262
+ {
2263
+ source: source || "custom",
2264
+ name,
2265
+ url,
2266
+ thumbnail: thumbnail || resolvedSongs.find((s) => s.thumbnail)?.thumbnail,
2267
+ songs: resolvedSongs
2268
+ },
2269
+ { member, metadata }
2270
+ );
2271
+ }
2272
+ /**
2273
+ * Get the guild queue
2274
+ * @param guild - The type can be resolved to give a {@link Queue}
2275
+ */
2276
+ getQueue(guild) {
2277
+ return this.queues.get(guild);
2278
+ }
2279
+ #getQueue(guild) {
2280
+ const queue = this.getQueue(guild);
2281
+ if (!queue) throw new DisTubeError("NO_QUEUE");
2282
+ return queue;
2283
+ }
2284
+ /**
2285
+ * Pause the guild stream
2286
+ * @param guild - The type can be resolved to give a {@link Queue}
2287
+ * @returns The guild queue
2288
+ */
2289
+ pause(guild) {
2290
+ return this.#getQueue(guild).pause();
2291
+ }
2292
+ /**
2293
+ * Resume the guild stream
2294
+ * @param guild - The type can be resolved to give a {@link Queue}
2295
+ * @returns The guild queue
2296
+ */
2297
+ resume(guild) {
2298
+ return this.#getQueue(guild).resume();
2299
+ }
2300
+ /**
2301
+ * Stop the guild stream
2302
+ * @param guild - The type can be resolved to give a {@link Queue}
2303
+ */
2304
+ stop(guild) {
2305
+ return this.#getQueue(guild).stop();
2306
+ }
2307
+ /**
2308
+ * Set the guild stream's volume
2309
+ * @param guild - The type can be resolved to give a {@link Queue}
2310
+ * @param percent - The percentage of volume you want to set
2311
+ * @returns The guild queue
2312
+ */
2313
+ setVolume(guild, percent) {
2314
+ return this.#getQueue(guild).setVolume(percent);
2315
+ }
2316
+ /**
2317
+ * Skip the playing song if there is a next song in the queue. <info>If {@link
2318
+ * Queue#autoplay} is `true` and there is no up next song, DisTube will add and
2319
+ * play a related song.</info>
2320
+ * @param guild - The type can be resolved to give a {@link Queue}
2321
+ * @returns The new Song will be played
2322
+ */
2323
+ skip(guild) {
2324
+ return this.#getQueue(guild).skip();
2325
+ }
2326
+ /**
2327
+ * Play the previous song
2328
+ * @param guild - The type can be resolved to give a {@link Queue}
2329
+ * @returns The new Song will be played
2330
+ */
2331
+ previous(guild) {
2332
+ return this.#getQueue(guild).previous();
2333
+ }
2334
+ /**
2335
+ * Shuffle the guild queue songs
2336
+ * @param guild - The type can be resolved to give a {@link Queue}
2337
+ * @returns The guild queue
2338
+ */
2339
+ shuffle(guild) {
2340
+ return this.#getQueue(guild).shuffle();
2341
+ }
2342
+ /**
2343
+ * Jump to the song number in the queue. The next one is 1, 2,... The previous one
2344
+ * is -1, -2,...
2345
+ * @param guild - The type can be resolved to give a {@link Queue}
2346
+ * @param num - The song number to play
2347
+ * @returns The new Song will be played
2348
+ */
2349
+ jump(guild, num) {
2350
+ return this.#getQueue(guild).jump(num);
2351
+ }
2352
+ /**
2353
+ * Set the repeat mode of the guild queue.
2354
+ * Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
2355
+ * @param guild - The type can be resolved to give a {@link Queue}
2356
+ * @param mode - The repeat modes (toggle if `undefined`)
2357
+ * @returns The new repeat mode
2358
+ */
2359
+ setRepeatMode(guild, mode) {
2360
+ return this.#getQueue(guild).setRepeatMode(mode);
2361
+ }
2362
+ /**
2363
+ * Toggle autoplay mode
2364
+ * @param guild - The type can be resolved to give a {@link Queue}
2365
+ * @returns Autoplay mode state
2366
+ */
2367
+ toggleAutoplay(guild) {
2368
+ const queue = this.#getQueue(guild);
2369
+ queue.autoplay = !queue.autoplay;
2370
+ return queue.autoplay;
2371
+ }
2372
+ /**
2373
+ * Add related song to the queue
2374
+ * @param guild - The type can be resolved to give a {@link Queue}
2375
+ * @returns The guild queue
2376
+ */
2377
+ addRelatedSong(guild) {
2378
+ return this.#getQueue(guild).addRelatedSong();
2379
+ }
2380
+ /**
2381
+ * Set the playing time to another position
2382
+ * @param guild - The type can be resolved to give a {@link Queue}
2383
+ * @param time - Time in seconds
2384
+ * @returns Seeked queue
2385
+ */
2386
+ seek(guild, time) {
2387
+ return this.#getQueue(guild).seek(time);
2388
+ }
2389
+ /**
2390
+ * Emit error event
2391
+ * @param error - error
2392
+ * @param queue - The queue encountered the error
2393
+ * @param song - The playing song when encountered the error
2394
+ */
2395
+ emitError(error, queue, song) {
2396
+ this.emit("error" /* ERROR */, error, queue, song);
2397
+ }
2398
+ /**
2399
+ * Emit debug event
2400
+ * @param message - debug message
2401
+ */
2402
+ debug(message) {
2403
+ this.emit("debug" /* DEBUG */, message);
2404
+ }
2405
+ };
2406
+
2407
+ // src/index.ts
2408
+ var version = "5.0.4";
2409
+ export {
2410
+ BaseManager,
2411
+ DisTube,
2412
+ DisTubeBase,
2413
+ DisTubeError,
2414
+ DisTubeHandler,
2415
+ DisTubeStream,
2416
+ DisTubeVoice,
2417
+ DisTubeVoiceManager,
2418
+ Events,
2419
+ ExtractorPlugin,
2420
+ FilterManager,
2421
+ GuildIdManager,
2422
+ InfoExtractorPlugin,
2423
+ Options,
2424
+ PlayableExtractorPlugin,
2425
+ Playlist,
2426
+ Plugin,
2427
+ PluginType,
2428
+ Queue,
2429
+ QueueManager,
2430
+ RepeatMode,
2431
+ Song,
2432
+ TaskQueue,
2433
+ checkEncryptionLibraries,
2434
+ checkFFmpeg,
2435
+ checkIntents,
2436
+ checkInvalidKey,
2437
+ DisTube as default,
2438
+ defaultFilters,
2439
+ defaultOptions,
2440
+ formatDuration,
2441
+ isClientInstance,
2442
+ isGuildInstance,
2443
+ isMemberInstance,
2444
+ isMessageInstance,
2445
+ isNsfwChannel,
2446
+ isObject,
2447
+ isSnowflake,
2448
+ isSupportedVoiceChannel,
2449
+ isTextChannelInstance,
2450
+ isTruthy,
2451
+ isURL,
2452
+ isVoiceChannelEmpty,
2453
+ objectKeys,
2454
+ resolveGuildId,
2455
+ version
2456
+ };
2457
+ //# sourceMappingURL=index.mjs.map