distube 5.0.2 → 5.0.3

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