distube 4.0.4 → 4.0.5

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.js CHANGED
@@ -23,6 +23,10 @@ var __copyProps = (to, from, except, desc) => {
23
23
  return to;
24
24
  };
25
25
  var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
30
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
31
  mod
28
32
  ));
@@ -59,7 +63,7 @@ var require_package = __commonJS({
59
63
  "package.json"(exports, module2) {
60
64
  module2.exports = {
61
65
  name: "distube",
62
- version: "4.0.4",
66
+ version: "4.0.5",
63
67
  description: "A Discord.js module to simplify your music commands and play songs with audio filters on Discord without any API key.",
64
68
  main: "./dist/index.js",
65
69
  types: "./dist/index.d.ts",
@@ -83,7 +87,8 @@ var require_package = __commonJS({
83
87
  postinstall: "husky install",
84
88
  prepublishOnly: "yarn lint && yarn test",
85
89
  prepack: "yarn build && pinst --disable",
86
- postpack: "pinst --enable"
90
+ postpack: "pinst --enable",
91
+ "dev:add-docs-to-worktree": "git worktree add --track -b docs docs origin/docs"
87
92
  },
88
93
  repository: {
89
94
  type: "git",
@@ -119,44 +124,45 @@ var require_package = __commonJS({
119
124
  ],
120
125
  homepage: "https://distube.js.org/",
121
126
  dependencies: {
122
- "@distube/ytdl-core": "^4.11.3",
127
+ "@distube/ytdl-core": "^4.11.12",
123
128
  "@distube/ytpl": "^1.1.1",
124
- "@distube/ytsr": "^1.1.8",
129
+ "@distube/ytsr": "^1.1.9",
125
130
  "prism-media": "https://codeload.github.com/distubejs/prism-media/tar.gz/main#workaround.tar.gz",
126
131
  "tiny-typed-emitter": "^2.1.0",
127
- tslib: "^2.4.0",
128
- undici: "^5.8.0"
132
+ tslib: "^2.6.0",
133
+ undici: "^5.22.1"
129
134
  },
130
135
  devDependencies: {
131
- "@babel/core": "^7.18.9",
136
+ "@babel/core": "^7.22.9",
132
137
  "@babel/plugin-proposal-class-properties": "^7.18.6",
133
- "@babel/plugin-proposal-object-rest-spread": "^7.18.9",
134
- "@babel/preset-env": "^7.18.9",
135
- "@babel/preset-typescript": "^7.18.6",
136
- "@commitlint/cli": "^17.0.3",
137
- "@commitlint/config-conventional": "^17.0.3",
138
- "@discordjs/voice": "^0.11.0",
138
+ "@babel/plugin-proposal-object-rest-spread": "^7.20.7",
139
+ "@babel/plugin-transform-private-methods": "^7.22.5",
140
+ "@babel/preset-env": "^7.22.9",
141
+ "@babel/preset-typescript": "^7.22.5",
142
+ "@commitlint/cli": "^17.6.7",
143
+ "@commitlint/config-conventional": "^17.6.7",
144
+ "@discordjs/voice": "^0.16.0",
139
145
  "@distubejs/docgen": "distubejs/docgen",
140
- "@types/jest": "^28.1.6",
141
- "@types/node": "^18.6.1",
142
- "@typescript-eslint/eslint-plugin": "^5.31.0",
143
- "@typescript-eslint/parser": "^5.31.0",
144
- "babel-jest": "^28.1.3",
145
- "discord.js": "^14.0.3",
146
- eslint: "^8.20.0",
146
+ "@types/jest": "^29.5.3",
147
+ "@types/node": "^20.4.2",
148
+ "@typescript-eslint/eslint-plugin": "^6.1.0",
149
+ "@typescript-eslint/parser": "^6.1.0",
150
+ "babel-jest": "^29.6.1",
151
+ "discord.js": "^14.11.0",
152
+ eslint: "^8.45.0",
147
153
  "eslint-config-distube": "^1.6.4",
148
- "eslint-config-prettier": "^8.5.0",
149
- "eslint-plugin-deprecation": "^1.3.2",
150
- "eslint-plugin-jsdoc": "^39.3.3",
151
- husky: "^8.0.1",
152
- jest: "^28.1.3",
154
+ "eslint-config-prettier": "^8.8.0",
155
+ "eslint-plugin-deprecation": "^1.4.1",
156
+ "eslint-plugin-jsdoc": "^46.4.4",
157
+ husky: "^8.0.3",
158
+ jest: "^29.6.1",
153
159
  "jsdoc-babel": "^0.5.0",
154
160
  "nano-staged": "^0.8.0",
155
- "npm-check-updates": "^16.0.0",
161
+ "npm-check-updates": "^16.10.16",
156
162
  pinst: "^3.0.0",
157
- prettier: "^2.7.1",
158
- tsup: "^6.2.0",
159
- typescript: "^4.7.4"
163
+ prettier: "^3.0.0",
164
+ tsup: "^7.1.0",
165
+ typescript: "^5.1.6"
160
166
  },
161
167
  peerDependencies: {
162
168
  "@discordjs/opus": "*",
@@ -180,7 +186,7 @@ var require_package = __commonJS({
180
186
  engines: {
181
187
  node: ">=16.9.0"
182
188
  },
183
- packageManager: "yarn@3.2.0"
189
+ packageManager: "yarn@3.6.1"
184
190
  };
185
191
  }
186
192
  });
@@ -226,6 +232,7 @@ __export(src_exports, {
226
232
  isGuildInstance: () => isGuildInstance,
227
233
  isMemberInstance: () => isMemberInstance,
228
234
  isMessageInstance: () => isMessageInstance,
235
+ isNsfwChannel: () => isNsfwChannel,
229
236
  isObject: () => isObject,
230
237
  isRecord: () => isRecord,
231
238
  isSnowflake: () => isSnowflake,
@@ -364,13 +371,13 @@ var ERROR_MESSAGES = {
364
371
  var haveCode = /* @__PURE__ */ __name((code) => Object.keys(ERROR_MESSAGES).includes(code), "haveCode");
365
372
  var parseMessage = /* @__PURE__ */ __name((m, ...args) => typeof m === "string" ? m : m(...args), "parseMessage");
366
373
  var getErrorMessage = /* @__PURE__ */ __name((code, ...args) => haveCode(code) ? parseMessage(ERROR_MESSAGES[code], ...args) : args[0], "getErrorMessage");
367
- var DisTubeError = class extends Error {
374
+ var _DisTubeError = class _DisTubeError extends Error {
368
375
  constructor(code, ...args) {
369
376
  super(getErrorMessage(code, ...args));
370
377
  __publicField(this, "errorCode");
371
378
  this.errorCode = code;
372
379
  if (Error.captureStackTrace)
373
- Error.captureStackTrace(this, DisTubeError);
380
+ Error.captureStackTrace(this, _DisTubeError);
374
381
  }
375
382
  get name() {
376
383
  return `DisTubeError [${this.errorCode}]`;
@@ -379,10 +386,11 @@ var DisTubeError = class extends Error {
379
386
  return this.errorCode;
380
387
  }
381
388
  };
382
- __name(DisTubeError, "DisTubeError");
389
+ __name(_DisTubeError, "DisTubeError");
390
+ var DisTubeError = _DisTubeError;
383
391
 
384
392
  // src/struct/TaskQueue.ts
385
- var Task = class {
393
+ var _Task = class _Task {
386
394
  constructor(resolveInfo) {
387
395
  __publicField(this, "resolve");
388
396
  __publicField(this, "promise");
@@ -393,33 +401,64 @@ var Task = class {
393
401
  });
394
402
  }
395
403
  };
396
- __name(Task, "Task");
404
+ __name(_Task, "Task");
405
+ var Task = _Task;
397
406
  var _tasks;
398
- var TaskQueue = class {
407
+ var _TaskQueue = class _TaskQueue {
399
408
  constructor() {
409
+ /**
410
+ * The task array
411
+ * @type {Task[]}
412
+ * @private
413
+ */
400
414
  __privateAdd(this, _tasks, []);
401
415
  }
416
+ /**
417
+ * Waits for last task finished and queues a new task
418
+ * @param {boolean} [resolveInfo=false] Whether the task is a resolving info task
419
+ * @returns {Promise<void>}
420
+ */
402
421
  queuing(resolveInfo = false) {
403
422
  const next = this.remaining ? __privateGet(this, _tasks)[__privateGet(this, _tasks).length - 1].promise : Promise.resolve();
404
423
  __privateGet(this, _tasks).push(new Task(resolveInfo));
405
424
  return next;
406
425
  }
426
+ /**
427
+ * Removes the finished task and processes the next task
428
+ */
407
429
  resolve() {
408
430
  __privateGet(this, _tasks).shift()?.resolve();
409
431
  }
432
+ /**
433
+ * The remaining number of tasks
434
+ * @type {number}
435
+ */
410
436
  get remaining() {
411
437
  return __privateGet(this, _tasks).length;
412
438
  }
439
+ /**
440
+ * Whether or not having a resolving info task
441
+ * @type {boolean}
442
+ */
413
443
  get hasResolveTask() {
414
444
  return !!__privateGet(this, _tasks).find((t) => t.resolveInfo);
415
445
  }
416
446
  };
417
- __name(TaskQueue, "TaskQueue");
418
447
  _tasks = new WeakMap();
448
+ __name(_TaskQueue, "TaskQueue");
449
+ var TaskQueue = _TaskQueue;
419
450
 
420
451
  // src/struct/Playlist.ts
421
452
  var _metadata, _member;
422
- var Playlist = class {
453
+ var _Playlist = class _Playlist {
454
+ /**
455
+ * Create a playlist
456
+ * @param {Song[]|PlaylistInfo} playlist Playlist
457
+ * @param {Object} [options] Optional options
458
+ * @param {Discord.GuildMember} [options.member] Requested user
459
+ * @param {Object} [options.properties] Custom properties
460
+ * @param {T} [options.metadata] Playlist metadata
461
+ */
423
462
  constructor(playlist, options = {}) {
424
463
  __publicField(this, "source");
425
464
  __publicField(this, "songs");
@@ -448,7 +487,8 @@ var Playlist = class {
448
487
  if (!Array.isArray(playlist.songs) || !playlist.songs.length)
449
488
  throw new DisTubeError("EMPTY_PLAYLIST");
450
489
  this.songs = playlist.songs;
451
- this.name = playlist.name || playlist.title || (this.songs[0].name ? `${this.songs[0].name} and ${this.songs.length - 1} more songs.` : `${this.songs.length} songs playlist`);
490
+ this.name = playlist.name || // eslint-disable-next-line deprecation/deprecation
491
+ playlist.title || (this.songs[0].name ? `${this.songs[0].name} and ${this.songs.length - 1} more songs.` : `${this.songs.length} songs playlist`);
452
492
  this.url = playlist.url || playlist.webpage_url;
453
493
  this.thumbnail = playlist.thumbnail || this.songs[0].thumbnail;
454
494
  this.member = member || playlist.member || void 0;
@@ -459,12 +499,24 @@ var Playlist = class {
459
499
  this[key] = value;
460
500
  this.metadata = metadata;
461
501
  }
502
+ /**
503
+ * Playlist duration in second.
504
+ * @type {number}
505
+ */
462
506
  get duration() {
463
507
  return this.songs?.reduce((prev, next) => prev + (next.duration || 0), 0) || 0;
464
508
  }
509
+ /**
510
+ * Formatted duration string `hh:mm:ss`.
511
+ * @type {string}
512
+ */
465
513
  get formattedDuration() {
466
514
  return formatDuration(this.duration);
467
515
  }
516
+ /**
517
+ * User requested.
518
+ * @type {Discord.GuildMember?}
519
+ */
468
520
  get member() {
469
521
  return __privateGet(this, _member);
470
522
  }
@@ -474,6 +526,10 @@ var Playlist = class {
474
526
  __privateSet(this, _member, member);
475
527
  this.songs.map((s) => s.constructor.name === "Song" && (s.member = this.member));
476
528
  }
529
+ /**
530
+ * User requested.
531
+ * @type {Discord.User?}
532
+ */
477
533
  get user() {
478
534
  return this.member?.user;
479
535
  }
@@ -485,12 +541,17 @@ var Playlist = class {
485
541
  this.songs.map((s) => s.constructor.name === "Song" && (s.metadata = metadata));
486
542
  }
487
543
  };
488
- __name(Playlist, "Playlist");
489
544
  _metadata = new WeakMap();
490
545
  _member = new WeakMap();
546
+ __name(_Playlist, "Playlist");
547
+ var Playlist = _Playlist;
491
548
 
492
549
  // src/struct/SearchResult.ts
493
- var ISearchResult = class {
550
+ var _ISearchResult = class _ISearchResult {
551
+ /**
552
+ * Create a search result
553
+ * @param {Object} info ytsr result
554
+ */
494
555
  constructor(info) {
495
556
  __publicField(this, "source");
496
557
  __publicField(this, "id");
@@ -507,8 +568,9 @@ var ISearchResult = class {
507
568
  };
508
569
  }
509
570
  };
510
- __name(ISearchResult, "ISearchResult");
511
- var SearchResultVideo = class extends ISearchResult {
571
+ __name(_ISearchResult, "ISearchResult");
572
+ var ISearchResult = _ISearchResult;
573
+ var _SearchResultVideo = class _SearchResultVideo extends ISearchResult {
512
574
  constructor(info) {
513
575
  super(info);
514
576
  __publicField(this, "type");
@@ -531,8 +593,9 @@ var SearchResultVideo = class extends ISearchResult {
531
593
  };
532
594
  }
533
595
  };
534
- __name(SearchResultVideo, "SearchResultVideo");
535
- var SearchResultPlaylist = class extends ISearchResult {
596
+ __name(_SearchResultVideo, "SearchResultVideo");
597
+ var SearchResultVideo = _SearchResultVideo;
598
+ var _SearchResultPlaylist = class _SearchResultPlaylist extends ISearchResult {
536
599
  constructor(info) {
537
600
  super(info);
538
601
  __publicField(this, "type");
@@ -547,11 +610,20 @@ var SearchResultPlaylist = class extends ISearchResult {
547
610
  };
548
611
  }
549
612
  };
550
- __name(SearchResultPlaylist, "SearchResultPlaylist");
613
+ __name(_SearchResultPlaylist, "SearchResultPlaylist");
614
+ var SearchResultPlaylist = _SearchResultPlaylist;
551
615
 
552
616
  // src/struct/Song.ts
553
617
  var _metadata2, _member2, _playlist;
554
- var _Song = class {
618
+ var _Song = class _Song {
619
+ /**
620
+ * Create a Song
621
+ * @param {ytdl.videoInfo|SearchResult|OtherSongInfo} info Raw info
622
+ * @param {Object} [options] Optional options
623
+ * @param {Discord.GuildMember} [options.member] Requested user
624
+ * @param {string} [options.source="youtube"] Song source
625
+ * @param {T} [options.metadata] Song metadata
626
+ */
555
627
  constructor(info, options = {}) {
556
628
  __publicField(this, "source");
557
629
  __privateAdd(this, _metadata2, void 0);
@@ -625,6 +697,11 @@ var _Song = class {
625
697
  this.chapters = details.chapters || [];
626
698
  this.reposts = 0;
627
699
  }
700
+ /**
701
+ * Patch data from other source
702
+ * @param {OtherSongInfo} info Video info
703
+ * @private
704
+ */
628
705
  _patchOther(info) {
629
706
  this.id = info.id;
630
707
  this.name = info.title || info.name;
@@ -655,6 +732,10 @@ var _Song = class {
655
732
  this.age_restricted = info.age_restricted || !!info.age_limit && parseNumber(info.age_limit) >= 18;
656
733
  this.chapters = info.chapters || [];
657
734
  }
735
+ /**
736
+ * The playlist added this song
737
+ * @type {Playlist?}
738
+ */
658
739
  get playlist() {
659
740
  return __privateGet(this, _playlist);
660
741
  }
@@ -664,6 +745,10 @@ var _Song = class {
664
745
  __privateSet(this, _playlist, playlist);
665
746
  this.member = playlist.member;
666
747
  }
748
+ /**
749
+ * User requested.
750
+ * @type {Discord.GuildMember?}
751
+ */
667
752
  get member() {
668
753
  return __privateGet(this, _member2);
669
754
  }
@@ -671,6 +756,10 @@ var _Song = class {
671
756
  if (isMemberInstance(member))
672
757
  __privateSet(this, _member2, member);
673
758
  }
759
+ /**
760
+ * User requested.
761
+ * @type {Discord.User?}
762
+ */
674
763
  get user() {
675
764
  return this.member?.user;
676
765
  }
@@ -681,48 +770,85 @@ var _Song = class {
681
770
  __privateSet(this, _metadata2, metadata);
682
771
  }
683
772
  };
684
- var Song = _Song;
685
- __name(Song, "Song");
686
773
  _metadata2 = new WeakMap();
687
774
  _member2 = new WeakMap();
688
775
  _playlist = new WeakMap();
776
+ __name(_Song, "Song");
777
+ var Song = _Song;
689
778
 
690
779
  // src/core/DisTubeBase.ts
691
- var DisTubeBase = class {
780
+ var _DisTubeBase = class _DisTubeBase {
692
781
  constructor(distube) {
693
782
  __publicField(this, "distube");
694
783
  this.distube = distube;
695
784
  }
785
+ /**
786
+ * Emit the {@link DisTube} of this base
787
+ * @param {string} eventName Event name
788
+ * @param {...any} args arguments
789
+ * @returns {boolean}
790
+ */
696
791
  emit(eventName, ...args) {
697
792
  return this.distube.emit(eventName, ...args);
698
793
  }
794
+ /**
795
+ * Emit error event
796
+ * @param {Error} error error
797
+ * @param {Discord.BaseGuildTextChannel} [channel] Text channel where the error is encountered.
798
+ */
699
799
  emitError(error, channel) {
700
800
  this.distube.emitError(error, channel);
701
801
  }
802
+ /**
803
+ * The queue manager
804
+ * @type {QueueManager}
805
+ * @readonly
806
+ */
702
807
  get queues() {
703
808
  return this.distube.queues;
704
809
  }
810
+ /**
811
+ * The voice manager
812
+ * @type {DisTubeVoiceManager}
813
+ * @readonly
814
+ */
705
815
  get voices() {
706
816
  return this.distube.voices;
707
817
  }
818
+ /**
819
+ * Discord.js client
820
+ * @type {Discord.Client}
821
+ * @readonly
822
+ */
708
823
  get client() {
709
824
  return this.distube.client;
710
825
  }
826
+ /**
827
+ * DisTube options
828
+ * @type {DisTubeOptions}
829
+ * @readonly
830
+ */
711
831
  get options() {
712
832
  return this.distube.options;
713
833
  }
834
+ /**
835
+ * DisTube handler
836
+ * @type {DisTubeHandler}
837
+ * @readonly
838
+ */
714
839
  get handler() {
715
840
  return this.distube.handler;
716
841
  }
717
842
  };
718
- __name(DisTubeBase, "DisTubeBase");
843
+ __name(_DisTubeBase, "DisTubeBase");
844
+ var DisTubeBase = _DisTubeBase;
719
845
 
720
846
  // src/core/DisTubeVoice.ts
721
847
  var import_discord = require("discord.js");
722
848
  var import_tiny_typed_emitter = require("tiny-typed-emitter");
723
849
  var import_voice = require("@discordjs/voice");
724
850
  var _channel, _volume, _br, br_fn, _join, join_fn;
725
- var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
851
+ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedEmitter {
726
852
  constructor(voiceManager, channel) {
727
853
  super();
728
854
  __privateAdd(this, _br);
@@ -752,26 +878,37 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
752
878
  this.emit("error", error);
753
879
  });
754
880
  this.connection.on(import_voice.VoiceConnectionStatus.Disconnected, (_, newState) => {
755
- if (newState.reason === import_voice.VoiceConnectionDisconnectReason.Manual) {
756
- this.leave();
757
- } else if (newState.reason === import_voice.VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
881
+ if (newState.reason === import_voice.VoiceConnectionDisconnectReason.Manual)
882
+ return this.leave();
883
+ if (newState.reason === import_voice.VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
758
884
  (0, import_voice.entersState)(this.connection, import_voice.VoiceConnectionStatus.Connecting, 5e3).catch(() => {
759
885
  if (![import_voice.VoiceConnectionStatus.Ready, import_voice.VoiceConnectionStatus.Connecting].includes(this.connection.state.status)) {
760
886
  this.leave();
761
887
  }
762
888
  });
763
- } else if (this.connection.rejoinAttempts < 5) {
764
- setTimeout(() => {
765
- this.connection.rejoin();
766
- }, (this.connection.rejoinAttempts + 1) * 5e3).unref();
767
- } else if (this.connection.state.status !== import_voice.VoiceConnectionStatus.Destroyed) {
768
- this.leave(new DisTubeError("VOICE_RECONNECT_FAILED"));
889
+ return;
890
+ }
891
+ if (this.connection.rejoinAttempts < 5) {
892
+ setTimeout(
893
+ () => {
894
+ this.connection.rejoin();
895
+ },
896
+ (this.connection.rejoinAttempts + 1) * 5e3
897
+ ).unref();
898
+ return;
899
+ }
900
+ if (this.connection.state.status !== import_voice.VoiceConnectionStatus.Destroyed) {
901
+ return this.leave(new DisTubeError("VOICE_RECONNECT_FAILED"));
769
902
  }
770
903
  }).on(import_voice.VoiceConnectionStatus.Destroyed, () => {
771
904
  this.leave();
772
905
  }).on("error", () => void 0);
773
906
  this.connection.subscribe(this.audioPlayer);
774
907
  }
908
+ /**
909
+ * The voice channel id the bot is in
910
+ * @type {Snowflake?}
911
+ */
775
912
  get channelId() {
776
913
  return this.connection?.joinConfig?.channelId ?? void 0;
777
914
  }
@@ -811,6 +948,11 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
811
948
  __privateSet(this, _channel, channel);
812
949
  __privateMethod(this, _br, br_fn).call(this);
813
950
  }
951
+ /**
952
+ * Join a voice channel with this connection
953
+ * @param {Discord.BaseGuildVoiceChannel} [channel] A voice channel
954
+ * @returns {Promise<DisTubeVoice>}
955
+ */
814
956
  async join(channel) {
815
957
  const TIMEOUT = 3e4;
816
958
  if (channel)
@@ -827,6 +969,10 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
827
969
  }
828
970
  return this;
829
971
  }
972
+ /**
973
+ * Leave the voice channel of this connection
974
+ * @param {Error} [error] Optional, an error to emit with 'error' event.
975
+ */
830
976
  leave(error) {
831
977
  this.stop(true);
832
978
  if (!this.isDisconnected) {
@@ -837,9 +983,20 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
837
983
  this.connection.destroy();
838
984
  this.voices.remove(this.id);
839
985
  }
986
+ /**
987
+ * Stop the playing stream
988
+ * @param {boolean} [force=false] If true, will force the {@link DisTubeVoice#audioPlayer} to enter the Idle state
989
+ * even if the {@link DisTubeVoice#audioResource} has silence padding frames.
990
+ * @private
991
+ */
840
992
  stop(force = false) {
841
993
  this.audioPlayer.stop(force);
842
994
  }
995
+ /**
996
+ * Play a readable stream
997
+ * @private
998
+ * @param {DisTubeStream} stream Readable stream
999
+ */
843
1000
  play(stream) {
844
1001
  this.emittedError = false;
845
1002
  stream.stream.on("error", (error) => {
@@ -853,7 +1010,8 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
853
1010
  inlineVolume: true
854
1011
  });
855
1012
  this.volume = __privateGet(this, _volume);
856
- this.audioPlayer.play(this.audioResource);
1013
+ if (this.audioPlayer.state.status !== import_voice.AudioPlayerStatus.Paused)
1014
+ this.audioPlayer.play(this.audioResource);
857
1015
  }
858
1016
  set volume(volume) {
859
1017
  if (typeof volume !== "number" || isNaN(volume)) {
@@ -868,6 +1026,10 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
868
1026
  get volume() {
869
1027
  return __privateGet(this, _volume);
870
1028
  }
1029
+ /**
1030
+ * Playback duration of the audio resource in seconds
1031
+ * @type {number}
1032
+ */
871
1033
  get playbackDuration() {
872
1034
  return (this.audioResource?.playbackDuration ?? 0) / 1e3;
873
1035
  }
@@ -875,14 +1037,32 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
875
1037
  this.audioPlayer.pause();
876
1038
  }
877
1039
  unpause() {
1040
+ const state = this.audioPlayer.state;
1041
+ if (state.status !== import_voice.AudioPlayerStatus.Paused)
1042
+ return;
1043
+ if (this.audioResource && state.resource !== this.audioResource)
1044
+ return this.audioPlayer.play(this.audioResource);
878
1045
  this.audioPlayer.unpause();
879
1046
  }
1047
+ /**
1048
+ * Whether the bot is self-deafened
1049
+ * @type {boolean}
1050
+ */
880
1051
  get selfDeaf() {
881
1052
  return this.connection.joinConfig.selfDeaf;
882
1053
  }
1054
+ /**
1055
+ * Whether the bot is self-muted
1056
+ * @type {boolean}
1057
+ */
883
1058
  get selfMute() {
884
1059
  return this.connection.joinConfig.selfMute;
885
1060
  }
1061
+ /**
1062
+ * Self-deafens/undeafens the bot.
1063
+ * @param {boolean} selfDeaf Whether or not the bot should be self-deafened
1064
+ * @returns {boolean} true if the voice state was successfully updated, otherwise false
1065
+ */
886
1066
  setSelfDeaf(selfDeaf) {
887
1067
  if (typeof selfDeaf !== "boolean") {
888
1068
  throw new DisTubeError("INVALID_TYPE", "boolean", selfDeaf, "selfDeaf");
@@ -892,6 +1072,11 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
892
1072
  selfDeaf
893
1073
  });
894
1074
  }
1075
+ /**
1076
+ * Self-mutes/unmutes the bot.
1077
+ * @param {boolean} selfMute Whether or not the bot should be self-muted
1078
+ * @returns {boolean} true if the voice state was successfully updated, otherwise false
1079
+ */
895
1080
  setSelfMute(selfMute) {
896
1081
  if (typeof selfMute !== "boolean") {
897
1082
  throw new DisTubeError("INVALID_TYPE", "boolean", selfMute, "selfMute");
@@ -901,11 +1086,14 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
901
1086
  selfMute
902
1087
  });
903
1088
  }
1089
+ /**
1090
+ * The voice state of this connection
1091
+ * @type {Discord.VoiceState?}
1092
+ */
904
1093
  get voiceState() {
905
1094
  return this.channel?.guild?.members?.me?.voice;
906
1095
  }
907
1096
  };
908
- __name(DisTubeVoice, "DisTubeVoice");
909
1097
  _channel = new WeakMap();
910
1098
  _volume = new WeakMap();
911
1099
  _br = new WeakSet();
@@ -922,11 +1110,107 @@ join_fn = /* @__PURE__ */ __name(function(channel) {
922
1110
  group: channel.client.user?.id
923
1111
  });
924
1112
  }, "#join");
1113
+ __name(_DisTubeVoice, "DisTubeVoice");
1114
+ var DisTubeVoice = _DisTubeVoice;
1115
+
1116
+ // src/core/DisTubeStream.ts
1117
+ var import_prism_media = require("prism-media");
1118
+ var import_voice2 = require("@discordjs/voice");
1119
+ var chooseBestVideoFormat = /* @__PURE__ */ __name((formats, isLive = false) => {
1120
+ let filter = /* @__PURE__ */ __name((format) => format.hasAudio, "filter");
1121
+ if (isLive)
1122
+ filter = /* @__PURE__ */ __name((format) => format.hasAudio && format.isHLS, "filter");
1123
+ formats = formats.filter(filter).sort((a, b) => Number(b.audioBitrate) - Number(a.audioBitrate) || Number(a.bitrate) - Number(b.bitrate));
1124
+ return formats.find((format) => !format.hasVideo) || formats.sort((a, b) => Number(a.bitrate) - Number(b.bitrate))[0];
1125
+ }, "chooseBestVideoFormat");
1126
+ var _DisTubeStream = class _DisTubeStream {
1127
+ /**
1128
+ * Create a DisTubeStream to play with {@link DisTubeVoice}
1129
+ * @param {string} url Stream URL
1130
+ * @param {StreamOptions} options Stream options
1131
+ * @private
1132
+ */
1133
+ constructor(url, options) {
1134
+ __publicField(this, "type");
1135
+ __publicField(this, "stream");
1136
+ __publicField(this, "url");
1137
+ this.url = url;
1138
+ this.type = !options.type ? import_voice2.StreamType.OggOpus : import_voice2.StreamType.Raw;
1139
+ const args = [
1140
+ "-reconnect",
1141
+ "1",
1142
+ "-reconnect_streamed",
1143
+ "1",
1144
+ "-reconnect_delay_max",
1145
+ "5",
1146
+ "-i",
1147
+ url,
1148
+ "-analyzeduration",
1149
+ "0",
1150
+ "-loglevel",
1151
+ "0",
1152
+ "-ar",
1153
+ "48000",
1154
+ "-ac",
1155
+ "2",
1156
+ "-f"
1157
+ ];
1158
+ if (!options.type) {
1159
+ args.push("opus", "-acodec", "libopus");
1160
+ } else {
1161
+ args.push("s16le");
1162
+ }
1163
+ if (typeof options.seek === "number" && options.seek > 0) {
1164
+ args.unshift("-ss", options.seek.toString());
1165
+ }
1166
+ if (Array.isArray(options.ffmpegArgs)) {
1167
+ args.push(...options.ffmpegArgs);
1168
+ }
1169
+ this.stream = new import_prism_media.FFmpeg({ args, shell: false });
1170
+ this.stream._readableState && (this.stream._readableState.highWaterMark = 1 << 25);
1171
+ }
1172
+ /**
1173
+ * Create a stream from ytdl video formats
1174
+ * @param {ytdl.videoFormat[]} formats ytdl video formats
1175
+ * @param {StreamOptions} options options
1176
+ * @returns {DisTubeStream}
1177
+ * @private
1178
+ */
1179
+ static YouTube(formats, options = {}) {
1180
+ if (!formats || !formats.length)
1181
+ throw new DisTubeError("UNAVAILABLE_VIDEO");
1182
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
1183
+ throw new DisTubeError("INVALID_TYPE", "object", options, "options");
1184
+ }
1185
+ const bestFormat = chooseBestVideoFormat(formats, options.isLive);
1186
+ if (!bestFormat)
1187
+ throw new DisTubeError("UNPLAYABLE_FORMATS");
1188
+ return new _DisTubeStream(bestFormat.url, options);
1189
+ }
1190
+ /**
1191
+ * Create a stream from a stream url
1192
+ * @param {string} url stream url
1193
+ * @param {StreamOptions} options options
1194
+ * @returns {DisTubeStream}
1195
+ * @private
1196
+ */
1197
+ static DirectLink(url, options = {}) {
1198
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
1199
+ throw new DisTubeError("INVALID_TYPE", "object", options, "options");
1200
+ }
1201
+ if (typeof url !== "string" || !isURL(url)) {
1202
+ throw new DisTubeError("INVALID_TYPE", "an URL", url);
1203
+ }
1204
+ return new _DisTubeStream(url, options);
1205
+ }
1206
+ };
1207
+ __name(_DisTubeStream, "DisTubeStream");
1208
+ var DisTubeStream = _DisTubeStream;
925
1209
 
926
1210
  // src/core/DisTubeHandler.ts
927
1211
  var import_ytpl = __toESM(require("@distube/ytpl"));
928
1212
  var import_ytdl_core = __toESM(require("@distube/ytdl-core"));
929
- var DisTubeHandler = class extends DisTubeBase {
1213
+ var _DisTubeHandler = class _DisTubeHandler extends DisTubeBase {
930
1214
  constructor(distube) {
931
1215
  super(distube);
932
1216
  const client = this.client;
@@ -976,11 +1260,23 @@ var DisTubeHandler = class extends DisTubeBase {
976
1260
  }
977
1261
  return options;
978
1262
  }
1263
+ /**
1264
+ * @param {string} url url
1265
+ * @param {boolean} [basic=false] getBasicInfo?
1266
+ * @returns {Promise<ytdl.videoInfo>}
1267
+ */
979
1268
  getYouTubeInfo(url, basic = false) {
980
1269
  if (basic)
981
1270
  return import_ytdl_core.default.getBasicInfo(url, this.ytdlOptions);
982
1271
  return import_ytdl_core.default.getInfo(url, this.ytdlOptions);
983
1272
  }
1273
+ /**
1274
+ * Resolve a url or a supported object to a {@link Song} or {@link Playlist}
1275
+ * @param {string|Song|SearchResult|Playlist} song URL | {@link Song}| {@link SearchResult} | {@link Playlist}
1276
+ * @param {ResolveOptions} [options] Optional options
1277
+ * @returns {Promise<Song|Playlist|null>} Resolved
1278
+ * @throws {DisTubeError}
1279
+ */
984
1280
  async resolve(song, options = {}) {
985
1281
  if (song instanceof Song || song instanceof Playlist) {
986
1282
  if ("metadata" in options)
@@ -1011,6 +1307,12 @@ var DisTubeHandler = class extends DisTubeBase {
1011
1307
  }
1012
1308
  throw new DisTubeError("CANNOT_RESOLVE_SONG", song);
1013
1309
  }
1310
+ /**
1311
+ * Resolve Song[] or YouTube playlist url to a Playlist
1312
+ * @param {Playlist|Song[]|string} playlist Resolvable playlist
1313
+ * @param {ResolvePlaylistOptions} options Optional options
1314
+ * @returns {Promise<Playlist>}
1315
+ */
1014
1316
  async resolvePlaylist(playlist, options = {}) {
1015
1317
  const { member, source, metadata } = { source: "youtube", ...options };
1016
1318
  if (playlist instanceof Playlist) {
@@ -1037,6 +1339,13 @@ var DisTubeHandler = class extends DisTubeBase {
1037
1339
  }
1038
1340
  return new Playlist(playlist, { member, properties: { source }, metadata });
1039
1341
  }
1342
+ /**
1343
+ * Search for a song, fire {@link DisTube#event:error} if not found.
1344
+ * @param {Discord.Message} message The original message from an user
1345
+ * @param {string} query The query string
1346
+ * @returns {Promise<SearchResult?>} Song info
1347
+ * @throws {DisTubeError}
1348
+ */
1040
1349
  async searchSong(message, query) {
1041
1350
  if (!isMessageInstance(message))
1042
1351
  throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
@@ -1047,7 +1356,7 @@ var DisTubeHandler = class extends DisTubeBase {
1047
1356
  const limit = this.options.searchSongs > 1 ? this.options.searchSongs : 1;
1048
1357
  const results = await this.distube.search(query, {
1049
1358
  limit,
1050
- safeSearch: this.options.nsfw ? false : !message.channel?.nsfw
1359
+ safeSearch: this.options.nsfw ? false : !isNsfwChannel(message.channel)
1051
1360
  }).catch(() => {
1052
1361
  if (!this.emit("searchNoResult", message, query)) {
1053
1362
  console.warn("searchNoResult event does not have any listeners! Emits `error` event instead.");
@@ -1058,6 +1367,17 @@ var DisTubeHandler = class extends DisTubeBase {
1058
1367
  return null;
1059
1368
  return this.createSearchMessageCollector(message, results, query);
1060
1369
  }
1370
+ /**
1371
+ * Create a message collector for selecting search results.
1372
+ *
1373
+ * Needed events: {@link DisTube#event:searchResult}, {@link DisTube#event:searchCancel},
1374
+ * {@link DisTube#event:searchInvalidAnswer}, {@link DisTube#event:searchDone}.
1375
+ * @param {Discord.Message} message The original message from an user
1376
+ * @param {Array<SearchResult|Song|Playlist>} results The search results
1377
+ * @param {string?} [query] The query string
1378
+ * @returns {Promise<SearchResult|Song|Playlist|null>} Selected result
1379
+ * @throws {DisTubeError}
1380
+ */
1061
1381
  async createSearchMessageCollector(message, results, query) {
1062
1382
  if (!isMessageInstance(message))
1063
1383
  throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
@@ -1110,19 +1430,26 @@ var DisTubeHandler = class extends DisTubeBase {
1110
1430
  }
1111
1431
  return result;
1112
1432
  }
1433
+ /**
1434
+ * Play or add a {@link Playlist} to the queue.
1435
+ * @param {Discord.BaseGuildVoiceChannel} voiceChannel A voice channel
1436
+ * @param {Playlist|string} playlist A YouTube playlist url | a Playlist
1437
+ * @param {PlayHandlerOptions} [options] Optional options
1438
+ * @returns {Promise<void>}
1439
+ * @throws {DisTubeError}
1440
+ */
1113
1441
  async playPlaylist(voiceChannel, playlist, options = {}) {
1114
1442
  const { textChannel, skip } = { skip: false, ...options };
1115
1443
  const position = Number(options.position) || (skip ? 1 : 0);
1116
1444
  if (!(playlist instanceof Playlist))
1117
1445
  throw new DisTubeError("INVALID_TYPE", "Playlist", playlist, "playlist");
1118
1446
  const queue = this.queues.get(voiceChannel);
1119
- if (!this.options.nsfw && !(queue?.textChannel || textChannel)?.nsfw) {
1447
+ const isNsfw = isNsfwChannel(queue?.textChannel || textChannel);
1448
+ if (!this.options.nsfw && !isNsfw)
1120
1449
  playlist.songs = playlist.songs.filter((s) => !s.age_restricted);
1121
- }
1122
1450
  if (!playlist.songs.length) {
1123
- if (!this.options.nsfw && !textChannel?.nsfw) {
1451
+ if (!this.options.nsfw && !isNsfw)
1124
1452
  throw new DisTubeError("EMPTY_FILTERED_PLAYLIST");
1125
- }
1126
1453
  throw new DisTubeError("EMPTY_PLAYLIST");
1127
1454
  }
1128
1455
  if (queue) {
@@ -1142,13 +1469,21 @@ var DisTubeHandler = class extends DisTubeBase {
1142
1469
  }
1143
1470
  }
1144
1471
  }
1472
+ /**
1473
+ * Play or add a {@link Song} to the queue.
1474
+ * @param {Discord.BaseGuildVoiceChannel} voiceChannel A voice channel
1475
+ * @param {Song} song A YouTube playlist url | a Playlist
1476
+ * @param {PlayHandlerOptions} [options] Optional options
1477
+ * @returns {Promise<void>}
1478
+ * @throws {DisTubeError}
1479
+ */
1145
1480
  async playSong(voiceChannel, song, options = {}) {
1146
1481
  if (!(song instanceof Song))
1147
1482
  throw new DisTubeError("INVALID_TYPE", "Song", song, "song");
1148
1483
  const { textChannel, skip } = { skip: false, ...options };
1149
1484
  const position = Number(options.position) || (skip ? 1 : 0);
1150
1485
  const queue = this.queues.get(voiceChannel);
1151
- if (!this.options.nsfw && song.age_restricted && !(queue?.textChannel || textChannel)?.nsfw) {
1486
+ if (!this.options.nsfw && song.age_restricted && !isNsfwChannel(queue?.textChannel || textChannel)) {
1152
1487
  throw new DisTubeError("NON_NSFW");
1153
1488
  }
1154
1489
  if (queue) {
@@ -1168,12 +1503,35 @@ var DisTubeHandler = class extends DisTubeBase {
1168
1503
  }
1169
1504
  }
1170
1505
  }
1506
+ /**
1507
+ * Get {@link Song}'s stream info and attach it to the song.
1508
+ * @param {Song} song A Song
1509
+ */
1510
+ async attachStreamInfo(song) {
1511
+ const { url, source, formats, streamURL, isLive } = song;
1512
+ if (source === "youtube") {
1513
+ if (!formats || !chooseBestVideoFormat(formats, isLive)) {
1514
+ song._patchYouTube(await this.handler.getYouTubeInfo(url));
1515
+ }
1516
+ } else if (!streamURL) {
1517
+ for (const plugin of [...this.distube.extractorPlugins, ...this.distube.customPlugins]) {
1518
+ if (await plugin.validate(url)) {
1519
+ const info = [plugin.getStreamURL(url), plugin.getRelatedSongs(url)];
1520
+ const result = await Promise.all(info);
1521
+ song.streamURL = result[0];
1522
+ song.related = result[1];
1523
+ break;
1524
+ }
1525
+ }
1526
+ }
1527
+ }
1171
1528
  };
1172
- __name(DisTubeHandler, "DisTubeHandler");
1529
+ __name(_DisTubeHandler, "DisTubeHandler");
1530
+ var DisTubeHandler = _DisTubeHandler;
1173
1531
 
1174
1532
  // src/core/DisTubeOptions.ts
1175
1533
  var _validateOptions, validateOptions_fn;
1176
- var Options = class {
1534
+ var _Options = class _Options {
1177
1535
  constructor(options) {
1178
1536
  __privateAdd(this, _validateOptions);
1179
1537
  __publicField(this, "plugins");
@@ -1222,7 +1580,6 @@ var Options = class {
1222
1580
  __privateMethod(this, _validateOptions, validateOptions_fn).call(this);
1223
1581
  }
1224
1582
  };
1225
- __name(Options, "Options");
1226
1583
  _validateOptions = new WeakSet();
1227
1584
  validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
1228
1585
  if (typeof options.emitNewSongOnly !== "boolean") {
@@ -1303,95 +1660,34 @@ validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
1303
1660
  throw new DisTubeError("INVALID_TYPE", "boolean", options.directLink, "DisTubeOptions.directLink");
1304
1661
  }
1305
1662
  }, "#validateOptions");
1306
-
1307
- // src/core/DisTubeStream.ts
1308
- var import_prism_media = require("prism-media");
1309
- var import_voice2 = require("@discordjs/voice");
1310
- var chooseBestVideoFormat = /* @__PURE__ */ __name((formats, isLive = false) => {
1311
- let filter = /* @__PURE__ */ __name((format) => format.hasAudio, "filter");
1312
- if (isLive)
1313
- filter = /* @__PURE__ */ __name((format) => format.hasAudio && format.isHLS, "filter");
1314
- formats = formats.filter(filter).sort((a, b) => Number(b.audioBitrate) - Number(a.audioBitrate) || Number(a.bitrate) - Number(b.bitrate));
1315
- return formats.find((format) => !format.hasVideo) || formats.sort((a, b) => Number(a.bitrate) - Number(b.bitrate))[0];
1316
- }, "chooseBestVideoFormat");
1317
- var DisTubeStream = class {
1318
- constructor(url, options) {
1319
- __publicField(this, "type");
1320
- __publicField(this, "stream");
1321
- __publicField(this, "url");
1322
- this.url = url;
1323
- this.type = !options.type ? import_voice2.StreamType.OggOpus : import_voice2.StreamType.Raw;
1324
- const args = [
1325
- "-reconnect",
1326
- "1",
1327
- "-reconnect_streamed",
1328
- "1",
1329
- "-reconnect_delay_max",
1330
- "5",
1331
- "-i",
1332
- url,
1333
- "-analyzeduration",
1334
- "0",
1335
- "-loglevel",
1336
- "0",
1337
- "-ar",
1338
- "48000",
1339
- "-ac",
1340
- "2",
1341
- "-f"
1342
- ];
1343
- if (!options.type) {
1344
- args.push("opus", "-acodec", "libopus");
1345
- } else {
1346
- args.push("s16le");
1347
- }
1348
- if (typeof options.seek === "number" && options.seek > 0) {
1349
- args.unshift("-ss", options.seek.toString());
1350
- }
1351
- if (Array.isArray(options.ffmpegArgs)) {
1352
- args.push(...options.ffmpegArgs);
1353
- }
1354
- this.stream = new import_prism_media.FFmpeg({ args, shell: false });
1355
- this.stream._readableState && (this.stream._readableState.highWaterMark = 1 << 25);
1356
- }
1357
- static YouTube(formats, options = {}) {
1358
- if (!formats || !formats.length)
1359
- throw new DisTubeError("UNAVAILABLE_VIDEO");
1360
- if (!options || typeof options !== "object" || Array.isArray(options)) {
1361
- throw new DisTubeError("INVALID_TYPE", "object", options, "options");
1362
- }
1363
- const bestFormat = chooseBestVideoFormat(formats, options.isLive);
1364
- if (!bestFormat)
1365
- throw new DisTubeError("UNPLAYABLE_FORMATS");
1366
- return new DisTubeStream(bestFormat.url, options);
1367
- }
1368
- static DirectLink(url, options = {}) {
1369
- if (!options || typeof options !== "object" || Array.isArray(options)) {
1370
- throw new DisTubeError("INVALID_TYPE", "object", options, "options");
1371
- }
1372
- if (typeof url !== "string" || !isURL(url)) {
1373
- throw new DisTubeError("INVALID_TYPE", "an URL", url);
1374
- }
1375
- return new DisTubeStream(url, options);
1376
- }
1377
- };
1378
- __name(DisTubeStream, "DisTubeStream");
1663
+ __name(_Options, "Options");
1664
+ var Options = _Options;
1379
1665
 
1380
1666
  // src/core/manager/BaseManager.ts
1381
1667
  var import_discord2 = require("discord.js");
1382
- var BaseManager = class extends DisTubeBase {
1668
+ var _BaseManager = class _BaseManager extends DisTubeBase {
1383
1669
  constructor() {
1384
1670
  super(...arguments);
1671
+ /**
1672
+ * The collection of items for this manager.
1673
+ * @type {Collection}
1674
+ * @name BaseManager#collection
1675
+ */
1385
1676
  __publicField(this, "collection", new import_discord2.Collection());
1386
1677
  }
1678
+ /**
1679
+ * The size of the collection.
1680
+ * @type {number}
1681
+ */
1387
1682
  get size() {
1388
1683
  return this.collection.size;
1389
1684
  }
1390
1685
  };
1391
- __name(BaseManager, "BaseManager");
1686
+ __name(_BaseManager, "BaseManager");
1687
+ var BaseManager = _BaseManager;
1392
1688
 
1393
1689
  // src/core/manager/GuildIdManager.ts
1394
- var GuildIdManager = class extends BaseManager {
1690
+ var _GuildIdManager = class _GuildIdManager extends BaseManager {
1395
1691
  add(idOrInstance, data) {
1396
1692
  const id = resolveGuildId(idOrInstance);
1397
1693
  const existing = this.get(id);
@@ -1409,11 +1705,30 @@ var GuildIdManager = class extends BaseManager {
1409
1705
  return this.collection.has(resolveGuildId(idOrInstance));
1410
1706
  }
1411
1707
  };
1412
- __name(GuildIdManager, "GuildIdManager");
1708
+ __name(_GuildIdManager, "GuildIdManager");
1709
+ var GuildIdManager = _GuildIdManager;
1413
1710
 
1414
1711
  // src/core/manager/DisTubeVoiceManager.ts
1415
1712
  var import_voice3 = require("@discordjs/voice");
1416
- var DisTubeVoiceManager = class extends GuildIdManager {
1713
+ var _DisTubeVoiceManager = class _DisTubeVoiceManager extends GuildIdManager {
1714
+ /**
1715
+ * Get a {@link DisTubeVoice}.
1716
+ * @method get
1717
+ * @memberof DisTubeVoiceManager#
1718
+ * @param {GuildIdResolvable} guild The queue resolvable to resolve
1719
+ * @returns {DisTubeVoice?}
1720
+ */
1721
+ /**
1722
+ * Collection of {@link DisTubeVoice}.
1723
+ * @name DisTubeVoiceManager#collection
1724
+ * @type {Discord.Collection<string, DisTubeVoice>}
1725
+ */
1726
+ /**
1727
+ * Create a {@link DisTubeVoice}
1728
+ * @param {Discord.BaseGuildVoiceChannel} channel A voice channel to join
1729
+ * @returns {DisTubeVoice}
1730
+ * @private
1731
+ */
1417
1732
  create(channel) {
1418
1733
  const existing = this.get(channel.guildId);
1419
1734
  if (existing) {
@@ -1422,12 +1737,21 @@ var DisTubeVoiceManager = class extends GuildIdManager {
1422
1737
  }
1423
1738
  return new DisTubeVoice(this, channel);
1424
1739
  }
1740
+ /**
1741
+ * Join a voice channel
1742
+ * @param {Discord.BaseGuildVoiceChannel} channel A voice channel to join
1743
+ * @returns {Promise<DisTubeVoice>}
1744
+ */
1425
1745
  join(channel) {
1426
1746
  const existing = this.get(channel.guildId);
1427
1747
  if (existing)
1428
1748
  return existing.join(channel);
1429
1749
  return this.create(channel).join();
1430
1750
  }
1751
+ /**
1752
+ * Leave the connected voice channel in a guild
1753
+ * @param {GuildIdResolvable} guild Queue Resolvable
1754
+ */
1431
1755
  leave(guild) {
1432
1756
  const voice = this.get(guild);
1433
1757
  if (voice) {
@@ -1440,20 +1764,33 @@ var DisTubeVoiceManager = class extends GuildIdManager {
1440
1764
  }
1441
1765
  }
1442
1766
  };
1443
- __name(DisTubeVoiceManager, "DisTubeVoiceManager");
1767
+ __name(_DisTubeVoiceManager, "DisTubeVoiceManager");
1768
+ var DisTubeVoiceManager = _DisTubeVoiceManager;
1444
1769
 
1445
1770
  // src/core/manager/FilterManager.ts
1446
- var _validate, validate_fn, _resolveName, resolveName_fn, _resolveValue, resolveValue_fn, _apply, apply_fn;
1447
- var FilterManager = class extends BaseManager {
1771
+ var _validate, validate_fn, _resolveName, resolveName_fn, _resolveValue, resolveValue_fn, _apply, apply_fn, _removeFn, removeFn_get;
1772
+ var _FilterManager = class _FilterManager extends BaseManager {
1448
1773
  constructor(queue) {
1449
1774
  super(queue.distube);
1450
1775
  __privateAdd(this, _validate);
1451
1776
  __privateAdd(this, _resolveName);
1452
1777
  __privateAdd(this, _resolveValue);
1453
1778
  __privateAdd(this, _apply);
1779
+ __privateAdd(this, _removeFn);
1780
+ /**
1781
+ * Collection of {@link FilterResolvable}.
1782
+ * @name FilterManager#collection
1783
+ * @type {Discord.Collection<string, DisTubeVoice>}
1784
+ */
1454
1785
  __publicField(this, "queue");
1455
1786
  this.queue = queue;
1456
1787
  }
1788
+ /**
1789
+ * Enable a filter or multiple filters to the manager
1790
+ * @param {FilterResolvable|FilterResolvable[]} filterOrFilters The filter or filters to enable
1791
+ * @param {boolean} [override=false] Wether or not override the applied filter with new filter value
1792
+ * @returns {FilterManager}
1793
+ */
1457
1794
  add(filterOrFilters, override = false) {
1458
1795
  if (Array.isArray(filterOrFilters)) {
1459
1796
  const resolvedFilters = filterOrFilters.map((f) => __privateMethod(this, _validate, validate_fn).call(this, f));
@@ -1469,13 +1806,31 @@ var FilterManager = class extends BaseManager {
1469
1806
  return unique;
1470
1807
  }, []).reverse();
1471
1808
  return this.set([...this.collection.values(), ...newFilters]);
1809
+ } else if (typeof filterOrFilters === "string") {
1810
+ return this.set([...this.collection.values(), filterOrFilters]);
1472
1811
  }
1473
- return this.set([...this.collection.values(), filterOrFilters]);
1812
+ throw new DisTubeError(
1813
+ "INVALID_TYPE",
1814
+ ["FilterResolvable", "Array<FilterResolvable>"],
1815
+ filterOrFilters,
1816
+ "filterOrFilters"
1817
+ );
1474
1818
  }
1819
+ /**
1820
+ * Clear enabled filters of the manager
1821
+ * @returns {FilterManager}
1822
+ */
1475
1823
  clear() {
1476
1824
  return this.set([]);
1477
1825
  }
1826
+ /**
1827
+ * Set the filters applied to the manager
1828
+ * @param {FilterResolvable[]} filters The filters to apply
1829
+ * @returns {FilterManager}
1830
+ */
1478
1831
  set(filters) {
1832
+ if (!Array.isArray(filters))
1833
+ throw new DisTubeError("INVALID_TYPE", "Array<FilterResolvable>", filters, "filters");
1479
1834
  this.collection.clear();
1480
1835
  for (const filter of filters) {
1481
1836
  const resolved = __privateMethod(this, _validate, validate_fn).call(this, filter);
@@ -1484,18 +1839,40 @@ var FilterManager = class extends BaseManager {
1484
1839
  __privateMethod(this, _apply, apply_fn).call(this);
1485
1840
  return this;
1486
1841
  }
1842
+ /**
1843
+ * Disable a filter or multiple filters
1844
+ * @param {FilterResolvable|FilterResolvable[]} filterOrFilters The filter or filters to disable
1845
+ * @returns {FilterManager}
1846
+ */
1487
1847
  remove(filterOrFilters) {
1488
- const remove = /* @__PURE__ */ __name((f) => this.collection.delete(__privateMethod(this, _resolveName, resolveName_fn).call(this, __privateMethod(this, _validate, validate_fn).call(this, f))), "remove");
1489
- if (Array.isArray(filterOrFilters))
1490
- filterOrFilters.map(remove);
1491
- else
1492
- remove(filterOrFilters);
1848
+ if (Array.isArray(filterOrFilters)) {
1849
+ filterOrFilters.map(__privateGet(this, _removeFn, removeFn_get));
1850
+ } else if (typeof filterOrFilters === "string") {
1851
+ __privateGet(this, _removeFn, removeFn_get).call(this, filterOrFilters);
1852
+ } else {
1853
+ throw new DisTubeError(
1854
+ "INVALID_TYPE",
1855
+ ["FilterResolvable", "Array<FilterResolvable>"],
1856
+ filterOrFilters,
1857
+ "filterOrFilters"
1858
+ );
1859
+ }
1493
1860
  __privateMethod(this, _apply, apply_fn).call(this);
1494
1861
  return this;
1495
1862
  }
1863
+ /**
1864
+ * Check whether a filter enabled or not
1865
+ * @param {FilterResolvable} filter The filter to check
1866
+ * @returns {boolean}
1867
+ */
1496
1868
  has(filter) {
1497
1869
  return this.collection.has(__privateMethod(this, _resolveName, resolveName_fn).call(this, filter));
1498
1870
  }
1871
+ /**
1872
+ * Array of enabled filter name
1873
+ * @type {Array<string>}
1874
+ * @readonly
1875
+ */
1499
1876
  get names() {
1500
1877
  return this.collection.map((f) => __privateMethod(this, _resolveName, resolveName_fn).call(this, f));
1501
1878
  }
@@ -1506,7 +1883,6 @@ var FilterManager = class extends BaseManager {
1506
1883
  return this.names.toString();
1507
1884
  }
1508
1885
  };
1509
- __name(FilterManager, "FilterManager");
1510
1886
  _validate = new WeakSet();
1511
1887
  validate_fn = /* @__PURE__ */ __name(function(filter) {
1512
1888
  if (typeof filter === "string" && Object.prototype.hasOwnProperty.call(this.distube.filters, filter) || typeof filter === "object" && typeof filter.name === "string" && typeof filter.value === "string") {
@@ -1527,17 +1903,66 @@ apply_fn = /* @__PURE__ */ __name(function() {
1527
1903
  this.queue.beginTime = this.queue.currentTime;
1528
1904
  this.queues.playSong(this.queue);
1529
1905
  }, "#apply");
1906
+ _removeFn = new WeakSet();
1907
+ removeFn_get = /* @__PURE__ */ __name(function() {
1908
+ return (f) => this.collection.delete(__privateMethod(this, _resolveName, resolveName_fn).call(this, __privateMethod(this, _validate, validate_fn).call(this, f)));
1909
+ }, "#removeFn");
1910
+ __name(_FilterManager, "FilterManager");
1911
+ var FilterManager = _FilterManager;
1530
1912
 
1531
1913
  // src/core/manager/QueueManager.ts
1532
- var _voiceEventHandler, voiceEventHandler_fn, _handleSongFinish, handleSongFinish_fn, _handlePlayingError, handlePlayingError_fn, _emitPlaySong, emitPlaySong_fn;
1533
- var QueueManager = class extends GuildIdManager {
1914
+ var _voiceEventHandler, voiceEventHandler_fn, _emitPlaySong, emitPlaySong_fn, _handleSongFinish, handleSongFinish_fn, _handlePlayingError, handlePlayingError_fn;
1915
+ var _QueueManager = class _QueueManager extends GuildIdManager {
1534
1916
  constructor() {
1535
1917
  super(...arguments);
1918
+ /**
1919
+ * Get a Queue from this QueueManager.
1920
+ * @method get
1921
+ * @memberof QueueManager#
1922
+ * @param {GuildIdResolvable} guild Resolvable thing from a guild
1923
+ * @returns {Queue?}
1924
+ */
1925
+ /**
1926
+ * Listen to DisTubeVoice events and handle the Queue
1927
+ * @private
1928
+ * @param {Queue} queue Queue
1929
+ */
1536
1930
  __privateAdd(this, _voiceEventHandler);
1931
+ /**
1932
+ * Whether or not emit playSong event
1933
+ * @param {Queue} queue Queue
1934
+ * @private
1935
+ * @returns {boolean}
1936
+ */
1937
+ __privateAdd(this, _emitPlaySong);
1938
+ /**
1939
+ * Handle the queue when a Song finish
1940
+ * @private
1941
+ * @param {Queue} queue queue
1942
+ * @returns {Promise<void>}
1943
+ */
1537
1944
  __privateAdd(this, _handleSongFinish);
1945
+ /**
1946
+ * Handle error while playing
1947
+ * @private
1948
+ * @param {Queue} queue queue
1949
+ * @param {Error} error error
1950
+ */
1538
1951
  __privateAdd(this, _handlePlayingError);
1539
- __privateAdd(this, _emitPlaySong);
1540
1952
  }
1953
+ /**
1954
+ * Collection of {@link Queue}.
1955
+ * @name QueueManager#collection
1956
+ * @type {Discord.Collection<string, Queue>}
1957
+ */
1958
+ /**
1959
+ * Create a {@link Queue}
1960
+ * @private
1961
+ * @param {Discord.BaseGuildVoiceChannel} channel A voice channel
1962
+ * @param {Song|Song[]} song First song
1963
+ * @param {Discord.BaseGuildTextChannel} textChannel Default text channel
1964
+ * @returns {Promise<Queue|true>} Returns `true` if encounter an error
1965
+ */
1541
1966
  async create(channel, song, textChannel) {
1542
1967
  if (this.has(channel.guildId))
1543
1968
  throw new DisTubeError("QUEUE_EXIST");
@@ -1555,6 +1980,11 @@ var QueueManager = class extends GuildIdManager {
1555
1980
  queue._taskQueue.resolve();
1556
1981
  }
1557
1982
  }
1983
+ /**
1984
+ * Create a ytdl stream
1985
+ * @param {Queue} queue Queue
1986
+ * @returns {DisTubeStream}
1987
+ */
1558
1988
  createStream(queue) {
1559
1989
  const { duration, formats, isLive, source, streamURL } = queue.songs[0];
1560
1990
  const ffmpegArgs = queue.filters.size ? ["-af", queue.filters.values.join(",")] : void 0;
@@ -1564,40 +1994,29 @@ var QueueManager = class extends GuildIdManager {
1564
1994
  return DisTubeStream.YouTube(formats, streamOptions);
1565
1995
  return DisTubeStream.DirectLink(streamURL, streamOptions);
1566
1996
  }
1997
+ /**
1998
+ * Play a song on voice connection
1999
+ * @private
2000
+ * @param {Queue} queue The guild queue
2001
+ * @returns {Promise<boolean>} error?
2002
+ */
1567
2003
  async playSong(queue) {
1568
2004
  if (!queue)
1569
2005
  return true;
1570
- if (!queue.songs.length) {
2006
+ if (queue.stopped || !queue.songs.length) {
1571
2007
  queue.stop();
1572
2008
  return true;
1573
2009
  }
1574
- if (queue.stopped)
1575
- return false;
1576
2010
  try {
1577
2011
  const song = queue.songs[0];
1578
- const { url, source, formats, streamURL, isLive } = song;
1579
- if (source === "youtube") {
1580
- if (!formats || !chooseBestVideoFormat(formats, isLive)) {
1581
- song._patchYouTube(await this.handler.getYouTubeInfo(url));
1582
- }
1583
- } else if (!streamURL) {
1584
- for (const plugin of [...this.distube.extractorPlugins, ...this.distube.customPlugins]) {
1585
- if (await plugin.validate(url)) {
1586
- const info = [plugin.getStreamURL(url), plugin.getRelatedSongs(url)];
1587
- const result = await Promise.all(info);
1588
- song.streamURL = result[0];
1589
- song.related = result[1];
1590
- break;
1591
- }
1592
- }
2012
+ await this.handler.attachStreamInfo(song);
2013
+ if (queue.stopped || !queue.songs.length) {
2014
+ queue.stop();
2015
+ return true;
1593
2016
  }
1594
2017
  const stream = this.createStream(queue);
1595
2018
  queue.voice.play(stream);
1596
2019
  song.streamURL = stream.url;
1597
- if (queue.stopped)
1598
- queue.stop();
1599
- else if (queue.paused)
1600
- queue.voice.pause();
1601
2020
  return false;
1602
2021
  } catch (e) {
1603
2022
  __privateMethod(this, _handlePlayingError, handlePlayingError_fn).call(this, queue, e);
@@ -1605,7 +2024,6 @@ var QueueManager = class extends GuildIdManager {
1605
2024
  }
1606
2025
  }
1607
2026
  };
1608
- __name(QueueManager, "QueueManager");
1609
2027
  _voiceEventHandler = new WeakSet();
1610
2028
  voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
1611
2029
  queue._listeners = {
@@ -1622,6 +2040,10 @@ voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
1622
2040
  queue.voice.on(event, queue._listeners[event]);
1623
2041
  }
1624
2042
  }, "#voiceEventHandler");
2043
+ _emitPlaySong = new WeakSet();
2044
+ emitPlaySong_fn = /* @__PURE__ */ __name(function(queue) {
2045
+ return !this.options.emitNewSongOnly || queue.repeatMode === 1 /* SONG */ && queue._next || queue.repeatMode !== 1 /* SONG */ && queue.songs[0]?.id !== queue.songs[1]?.id;
2046
+ }, "#emitPlaySong");
1625
2047
  _handleSongFinish = new WeakSet();
1626
2048
  handleSongFinish_fn = /* @__PURE__ */ __name(async function(queue) {
1627
2049
  this.emit("finishSong", queue, queue.songs[0]);
@@ -1695,14 +2117,19 @@ Name: ${song.name}`;
1695
2117
  queue.stop();
1696
2118
  }
1697
2119
  }, "#handlePlayingError");
1698
- _emitPlaySong = new WeakSet();
1699
- emitPlaySong_fn = /* @__PURE__ */ __name(function(queue) {
1700
- return !this.options.emitNewSongOnly || queue.repeatMode === 1 /* SONG */ && queue._next || queue.repeatMode !== 1 /* SONG */ && queue.songs[0]?.id !== queue.songs[1]?.id;
1701
- }, "#emitPlaySong");
2120
+ __name(_QueueManager, "QueueManager");
2121
+ var QueueManager = _QueueManager;
1702
2122
 
1703
2123
  // src/struct/Queue.ts
1704
2124
  var _filters;
1705
- var Queue = class extends DisTubeBase {
2125
+ var _Queue = class _Queue extends DisTubeBase {
2126
+ /**
2127
+ * Create a queue for the guild
2128
+ * @param {DisTube} distube DisTube
2129
+ * @param {DisTubeVoice} voice Voice connection
2130
+ * @param {Song|Song[]} song First song(s)
2131
+ * @param {Discord.BaseGuildTextChannel?} textChannel Default text channel
2132
+ */
1706
2133
  constructor(distube, voice, song, textChannel) {
1707
2134
  super(distube);
1708
2135
  __publicField(this, "id");
@@ -1741,24 +2168,58 @@ var Queue = class extends DisTubeBase {
1741
2168
  this._taskQueue = new TaskQueue();
1742
2169
  this._listeners = void 0;
1743
2170
  }
2171
+ /**
2172
+ * The client user as a `GuildMember` of this queue's guild
2173
+ * @type {Discord.GuildMember?}
2174
+ */
1744
2175
  get clientMember() {
1745
2176
  return this.voice.channel.guild.members.me ?? void 0;
1746
2177
  }
2178
+ /**
2179
+ * The filter manager of the queue
2180
+ * @type {FilterManager}
2181
+ * @readonly
2182
+ */
1747
2183
  get filters() {
1748
2184
  return __privateGet(this, _filters);
1749
2185
  }
2186
+ /**
2187
+ * Formatted duration string.
2188
+ * @type {string}
2189
+ * @readonly
2190
+ */
1750
2191
  get formattedDuration() {
1751
2192
  return formatDuration(this.duration);
1752
2193
  }
2194
+ /**
2195
+ * Queue's duration.
2196
+ * @type {number}
2197
+ * @readonly
2198
+ */
1753
2199
  get duration() {
1754
2200
  return this.songs.length ? this.songs.reduce((prev, next) => prev + next.duration, 0) : 0;
1755
2201
  }
2202
+ /**
2203
+ * What time in the song is playing (in seconds).
2204
+ * @type {number}
2205
+ * @readonly
2206
+ */
1756
2207
  get currentTime() {
1757
2208
  return this.voice.playbackDuration + this.beginTime;
1758
2209
  }
2210
+ /**
2211
+ * Formatted {@link Queue#currentTime} string.
2212
+ * @type {string}
2213
+ * @readonly
2214
+ */
1759
2215
  get formattedCurrentTime() {
1760
2216
  return formatDuration(this.currentTime);
1761
2217
  }
2218
+ /**
2219
+ * The voice channel playing in.
2220
+ * @type {Discord.VoiceChannel|Discord.StageChannel|null}
2221
+ * @readonly
2222
+ */
1762
2223
  get voiceChannel() {
1763
2224
  return this.clientMember?.voice?.channel ?? null;
1764
2225
  }
@@ -1768,6 +2229,14 @@ var Queue = class extends DisTubeBase {
1768
2229
  set volume(value) {
1769
2230
  this.voice.volume = value;
1770
2231
  }
2232
+ /**
2233
+ * @private
2234
+ * Add a Song or an array of Song to the queue
2235
+ * @param {Song|Song[]} song Song to add
2236
+ * @param {number} [position=0] Position to add, <= 0 to add to the end of the queue
2237
+ * @throws {Error}
2238
+ * @returns {Queue} The guild queue
2239
+ */
1771
2240
  addToQueue(song, position = 0) {
1772
2241
  if (!song || Array.isArray(song) && !song.length) {
1773
2242
  throw new DisTubeError("INVALID_TYPE", ["Song", "Array<Song>"], song, "song");
@@ -1791,6 +2260,10 @@ var Queue = class extends DisTubeBase {
1791
2260
  delete song.formats;
1792
2261
  return this;
1793
2262
  }
2263
+ /**
2264
+ * Pause the guild stream
2265
+ * @returns {Queue} The guild queue
2266
+ */
1794
2267
  pause() {
1795
2268
  if (this.paused)
1796
2269
  throw new DisTubeError("PAUSED");
@@ -1799,6 +2272,10 @@ var Queue = class extends DisTubeBase {
1799
2272
  this.voice.pause();
1800
2273
  return this;
1801
2274
  }
2275
+ /**
2276
+ * Resume the guild stream
2277
+ * @returns {Queue} The guild queue
2278
+ */
1802
2279
  resume() {
1803
2280
  if (this.playing)
1804
2281
  throw new DisTubeError("RESUMED");
@@ -1807,10 +2284,22 @@ var Queue = class extends DisTubeBase {
1807
2284
  this.voice.unpause();
1808
2285
  return this;
1809
2286
  }
2287
+ /**
2288
+ * Set the guild stream's volume
2289
+ * @param {number} percent The percentage of volume you want to set
2290
+ * @returns {Queue} The guild queue
2291
+ */
1810
2292
  setVolume(percent) {
1811
2293
  this.volume = percent;
1812
2294
  return this;
1813
2295
  }
2296
+ /**
2297
+ * Skip the playing song if there is a next song in the queue.
2298
+ * <info>If {@link Queue#autoplay} is `true` and there is no up next song,
2299
+ * DisTube will add and play a related song.</info>
2300
+ * @returns {Promise<Song>} The song will skip to
2301
+ * @throws {Error}
2302
+ */
1814
2303
  async skip() {
1815
2304
  await this._taskQueue.queuing();
1816
2305
  try {
@@ -1828,6 +2317,11 @@ var Queue = class extends DisTubeBase {
1828
2317
  this._taskQueue.resolve();
1829
2318
  }
1830
2319
  }
2320
+ /**
2321
+ * Play the previous song if exists
2322
+ * @returns {Promise<Song>} The guild queue
2323
+ * @throws {Error}
2324
+ */
1831
2325
  async previous() {
1832
2326
  await this._taskQueue.queuing();
1833
2327
  try {
@@ -1844,6 +2338,10 @@ var Queue = class extends DisTubeBase {
1844
2338
  this._taskQueue.resolve();
1845
2339
  }
1846
2340
  }
2341
+ /**
2342
+ * Shuffle the queue's songs
2343
+ * @returns {Promise<Queue>} The guild queue
2344
+ */
1847
2345
  async shuffle() {
1848
2346
  await this._taskQueue.queuing();
1849
2347
  try {
@@ -1860,6 +2358,14 @@ var Queue = class extends DisTubeBase {
1860
2358
  this._taskQueue.resolve();
1861
2359
  }
1862
2360
  }
2361
+ /**
2362
+ * Jump to the song position in the queue.
2363
+ * The next one is 1, 2,...
2364
+ * The previous one is -1, -2,...
2365
+ * @param {number} position The song position to play
2366
+ * @returns {Promise<Song>} The new Song will be played
2367
+ * @throws {Error} if `num` is invalid number
2368
+ */
1863
2369
  async jump(position) {
1864
2370
  await this._taskQueue.queuing();
1865
2371
  try {
@@ -1893,6 +2399,12 @@ var Queue = class extends DisTubeBase {
1893
2399
  this._taskQueue.resolve();
1894
2400
  }
1895
2401
  }
2402
+ /**
2403
+ * Set the repeat mode of the guild queue.\
2404
+ * Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
2405
+ * @param {RepeatMode?} [mode] The repeat modes (toggle if `undefined`)
2406
+ * @returns {RepeatMode} The new repeat mode
2407
+ */
1896
2408
  setRepeatMode(mode) {
1897
2409
  if (mode !== void 0 && !Object.values(RepeatMode).includes(mode)) {
1898
2410
  throw new DisTubeError("INVALID_TYPE", ["RepeatMode", "undefined"], mode, "mode");
@@ -1905,6 +2417,11 @@ var Queue = class extends DisTubeBase {
1905
2417
  this.repeatMode = mode;
1906
2418
  return this.repeatMode;
1907
2419
  }
2420
+ /**
2421
+ * Set the playing time to another position
2422
+ * @param {number} time Time in seconds
2423
+ * @returns {Queue} The guild queue
2424
+ */
1908
2425
  seek(time) {
1909
2426
  if (typeof time !== "number")
1910
2427
  throw new DisTubeError("INVALID_TYPE", "number", time, "time");
@@ -1914,6 +2431,11 @@ var Queue = class extends DisTubeBase {
1914
2431
  this.queues.playSong(this);
1915
2432
  return this;
1916
2433
  }
2434
+ /**
2435
+ * Add a related song of the playing song to the queue
2436
+ * @returns {Promise<Song>} The added song
2437
+ * @throws {Error}
2438
+ */
1917
2439
  async addRelatedSong() {
1918
2440
  if (!this.songs?.[0])
1919
2441
  throw new DisTubeError("NO_PLAYING");
@@ -1926,6 +2448,9 @@ var Queue = class extends DisTubeBase {
1926
2448
  this.addToQueue(song);
1927
2449
  return song;
1928
2450
  }
2451
+ /**
2452
+ * Stop the guild stream and delete the queue
2453
+ */
1929
2454
  async stop() {
1930
2455
  await this._taskQueue.queuing();
1931
2456
  try {
@@ -1941,6 +2466,11 @@ var Queue = class extends DisTubeBase {
1941
2466
  this._taskQueue.resolve();
1942
2467
  }
1943
2468
  }
2469
+ /**
2470
+ * Remove the queue from the manager
2471
+ * (This does not leave the voice channel even if {@link DisTubeOptions|DisTubeOptions.leaveOnStop} is enabled)
2472
+ * @private
2473
+ */
1944
2474
  remove() {
1945
2475
  this.stopped = true;
1946
2476
  this.songs = [];
@@ -1953,72 +2483,138 @@ var Queue = class extends DisTubeBase {
1953
2483
  this.queues.remove(this.id);
1954
2484
  this.emit("deleteQueue", this);
1955
2485
  }
2486
+ /**
2487
+ * Toggle autoplay mode
2488
+ * @returns {boolean} Autoplay mode state
2489
+ */
1956
2490
  toggleAutoplay() {
1957
2491
  this.autoplay = !this.autoplay;
1958
2492
  return this.autoplay;
1959
2493
  }
1960
2494
  };
1961
- __name(Queue, "Queue");
1962
2495
  _filters = new WeakMap();
2496
+ __name(_Queue, "Queue");
2497
+ var Queue = _Queue;
1963
2498
 
1964
2499
  // src/struct/Plugin.ts
1965
- var Plugin = class {
2500
+ var _Plugin = class _Plugin {
1966
2501
  constructor() {
1967
2502
  __publicField(this, "distube");
1968
2503
  }
1969
2504
  init(distube) {
1970
2505
  this.distube = distube;
1971
2506
  }
2507
+ /**
2508
+ * Type of the plugin
2509
+ * @name Plugin#type
2510
+ * @type {PluginType}
2511
+ */
2512
+ /**
2513
+ * Emit an event to the {@link DisTube} class
2514
+ * @param {string} eventName Event name
2515
+ * @param {...any} args arguments
2516
+ * @returns {boolean}
2517
+ */
1972
2518
  emit(eventName, ...args) {
1973
2519
  return this.distube.emit(eventName, ...args);
1974
2520
  }
2521
+ /**
2522
+ * Emit error event to the {@link DisTube} class
2523
+ * @param {Error} error error
2524
+ * @param {Discord.BaseGuildTextChannel} [channel] Text channel where the error is encountered.
2525
+ */
1975
2526
  emitError(error, channel) {
1976
2527
  this.distube.emitError(error, channel);
1977
2528
  }
2529
+ /**
2530
+ * The queue manager
2531
+ * @type {QueueManager}
2532
+ * @readonly
2533
+ */
1978
2534
  get queues() {
1979
2535
  return this.distube.queues;
1980
2536
  }
2537
+ /**
2538
+ * The voice manager
2539
+ * @type {DisTubeVoiceManager}
2540
+ * @readonly
2541
+ */
1981
2542
  get voices() {
1982
2543
  return this.distube.voices;
1983
2544
  }
2545
+ /**
2546
+ * Discord.js client
2547
+ * @type {Discord.Client}
2548
+ * @readonly
2549
+ */
1984
2550
  get client() {
1985
2551
  return this.distube.client;
1986
2552
  }
2553
+ /**
2554
+ * DisTube options
2555
+ * @type {DisTubeOptions}
2556
+ * @readonly
2557
+ */
1987
2558
  get options() {
1988
2559
  return this.distube.options;
1989
2560
  }
2561
+ /**
2562
+ * DisTube handler
2563
+ * @type {DisTubeHandler}
2564
+ * @readonly
2565
+ */
1990
2566
  get handler() {
1991
2567
  return this.distube.handler;
1992
2568
  }
2569
+ /**
2570
+ * Check if the string is working with this plugin
2571
+ * @param {string} _string Input string
2572
+ * @returns {boolean|Promise<boolean>}
2573
+ */
1993
2574
  validate(_string) {
1994
2575
  return false;
1995
2576
  }
2577
+ /**
2578
+ * Get the stream url from {@link Song#url}. Returns {@link Song#url} by default.
2579
+ * Not needed if the plugin plays song from YouTube.
2580
+ * @param {string} url Input url
2581
+ * @returns {string|Promise<string>}
2582
+ */
1996
2583
  getStreamURL(url) {
1997
2584
  return url;
1998
2585
  }
2586
+ /**
2587
+ * Get related songs from a supported url. {@link Song#member} should be `undefined`.
2588
+ * Not needed to add {@link Song#related} because it will be added with this function later.
2589
+ * @param {string} _url Input url
2590
+ * @returns {Song[]|Promise<Song[]>}
2591
+ */
1999
2592
  getRelatedSongs(_url) {
2000
2593
  return [];
2001
2594
  }
2002
2595
  };
2003
- __name(Plugin, "Plugin");
2596
+ __name(_Plugin, "Plugin");
2597
+ var Plugin = _Plugin;
2004
2598
 
2005
2599
  // src/struct/CustomPlugin.ts
2006
- var CustomPlugin = class extends Plugin {
2600
+ var _CustomPlugin = class _CustomPlugin extends Plugin {
2007
2601
  constructor() {
2008
2602
  super(...arguments);
2009
2603
  __publicField(this, "type", "custom" /* CUSTOM */);
2010
2604
  }
2011
2605
  };
2012
- __name(CustomPlugin, "CustomPlugin");
2606
+ __name(_CustomPlugin, "CustomPlugin");
2607
+ var CustomPlugin = _CustomPlugin;
2013
2608
 
2014
2609
  // src/struct/ExtractorPlugin.ts
2015
- var ExtractorPlugin = class extends Plugin {
2610
+ var _ExtractorPlugin = class _ExtractorPlugin extends Plugin {
2016
2611
  constructor() {
2017
2612
  super(...arguments);
2018
2613
  __publicField(this, "type", "extractor" /* EXTRACTOR */);
2019
2614
  }
2020
2615
  };
2021
- __name(ExtractorPlugin, "ExtractorPlugin");
2616
+ __name(_ExtractorPlugin, "ExtractorPlugin");
2617
+ var ExtractorPlugin = _ExtractorPlugin;
2022
2618
 
2023
2619
  // src/util.ts
2024
2620
  var import_url = require("url");
@@ -2027,7 +2623,7 @@ var formatInt = /* @__PURE__ */ __name((int) => int < 10 ? `0${int}` : int, "for
2027
2623
  function formatDuration(sec) {
2028
2624
  if (!sec || !Number(sec))
2029
2625
  return "00:00";
2030
- const seconds = Math.round(sec % 60);
2626
+ const seconds = Math.floor(sec % 60);
2031
2627
  const minutes = Math.floor(sec % 3600 / 60);
2032
2628
  const hours = Math.floor(sec / 3600);
2033
2629
  if (hours > 0)
@@ -2062,12 +2658,13 @@ function parseNumber(input) {
2062
2658
  return Number(input) || 0;
2063
2659
  }
2064
2660
  __name(parseNumber, "parseNumber");
2661
+ var SUPPORTED_PROTOCOL = ["https:", "http:", "file:"];
2065
2662
  function isURL(input) {
2066
2663
  if (typeof input !== "string" || input.includes(" "))
2067
2664
  return false;
2068
2665
  try {
2069
2666
  const url = new import_url.URL(input);
2070
- if (!["https:", "http:"].includes(url.protocol) || !url.host)
2667
+ if (!SUPPORTED_PROTOCOL.some((p) => p === url.protocol))
2071
2668
  return false;
2072
2669
  } catch {
2073
2670
  return false;
@@ -2106,7 +2703,7 @@ function isMemberInstance(member) {
2106
2703
  }
2107
2704
  __name(isMemberInstance, "isMemberInstance");
2108
2705
  function isTextChannelInstance(channel) {
2109
- return !!channel && isSnowflake(channel.id) && isSnowflake(channel.guildId) && typeof channel.name === "string" && import_discord3.Constants.TextBasedChannelTypes.includes(channel.type) && typeof channel.nsfw === "boolean" && "messages" in channel && typeof channel.send === "function";
2706
+ return !!channel && isSnowflake(channel.id) && isSnowflake(channel.guildId) && typeof channel.name === "string" && import_discord3.Constants.TextBasedChannelTypes.includes(channel.type) && "messages" in channel && typeof channel.send === "function";
2110
2707
  }
2111
2708
  __name(isTextChannelInstance, "isTextChannelInstance");
2112
2709
  function isMessageInstance(message) {
@@ -2166,20 +2763,30 @@ function objectKeys(obj) {
2166
2763
  return Object.keys(obj);
2167
2764
  }
2168
2765
  __name(objectKeys, "objectKeys");
2766
+ function isNsfwChannel(channel) {
2767
+ if (!isTextChannelInstance(channel))
2768
+ return false;
2769
+ if (channel.isThread())
2770
+ return channel.parent?.nsfw ?? false;
2771
+ return channel.nsfw;
2772
+ }
2773
+ __name(isNsfwChannel, "isNsfwChannel");
2169
2774
 
2170
2775
  // src/plugin/DirectLink.ts
2171
2776
  var import_undici = require("undici");
2172
- var DirectLinkPlugin = class extends ExtractorPlugin {
2777
+ var _DirectLinkPlugin = class _DirectLinkPlugin extends ExtractorPlugin {
2173
2778
  async validate(url) {
2174
2779
  try {
2175
2780
  const headers = await (0, import_undici.request)(url, { method: "HEAD" }).then((res) => res.headers);
2176
- const type = headers["content-type"];
2177
- if (type?.startsWith("audio"))
2781
+ const types = headers["content-type"];
2782
+ const type = Array.isArray(types) ? types[0] : types;
2783
+ if (["audio/", "video/", "application/ogg"].some((s) => type?.startsWith(s)))
2178
2784
  return true;
2179
2785
  } catch {
2180
2786
  }
2181
2787
  return false;
2182
2788
  }
2789
+ // eslint-disable-next-line @typescript-eslint/require-await
2183
2790
  async resolve(url, options = {}) {
2184
2791
  url = url.replace(/\/+$/, "");
2185
2792
  return new Song(
@@ -2192,13 +2799,28 @@ var DirectLinkPlugin = class extends ExtractorPlugin {
2192
2799
  );
2193
2800
  }
2194
2801
  };
2195
- __name(DirectLinkPlugin, "DirectLinkPlugin");
2802
+ __name(_DirectLinkPlugin, "DirectLinkPlugin");
2803
+ var DirectLinkPlugin = _DirectLinkPlugin;
2196
2804
 
2197
2805
  // src/DisTube.ts
2198
2806
  var import_ytsr = __toESM(require("@distube/ytsr"));
2199
2807
  var import_tiny_typed_emitter2 = require("tiny-typed-emitter");
2200
2808
  var { version } = require_package();
2201
- var DisTube = class extends import_tiny_typed_emitter2.TypedEmitter {
2809
+ var _DisTube = class _DisTube extends import_tiny_typed_emitter2.TypedEmitter {
2810
+ /**
2811
+ * Create a new DisTube class.
2812
+ * @param {Discord.Client} client Discord.JS client
2813
+ * @param {DisTubeOptions} [otp] Custom DisTube options
2814
+ * @throws {DisTubeError}
2815
+ * @example
2816
+ * const Discord = require('discord.js'),
2817
+ * DisTube = require('distube'),
2818
+ * client = new Discord.Client();
2819
+ * // Create a new DisTube
2820
+ * const distube = new DisTube.default(client, { searchSongs: 10 });
2821
+ * // client.DisTube = distube // make it access easily
2822
+ * client.login("Your Discord Bot Token")
2823
+ */
2202
2824
  constructor(client, otp = {}) {
2203
2825
  super();
2204
2826
  __publicField(this, "handler");
@@ -2228,9 +2850,36 @@ var DisTube = class extends import_tiny_typed_emitter2.TypedEmitter {
2228
2850
  static get version() {
2229
2851
  return version;
2230
2852
  }
2853
+ /**
2854
+ * DisTube version
2855
+ * @type {string}
2856
+ */
2231
2857
  get version() {
2232
2858
  return version;
2233
2859
  }
2860
+ /**
2861
+ * Play / add a song or playlist from url. Search and play a song if it is not a valid url.
2862
+ *
2863
+ * @param {Discord.BaseGuildVoiceChannel} voiceChannel The channel will be joined if the bot isn't in any channels,
2864
+ * the bot will be moved to this channel if {@link DisTubeOptions}.joinNewVoiceChannel is `true`
2865
+ * @param {string|Song|SearchResult|Playlist} song URL | Search string |
2866
+ * {@link Song} | {@link SearchResult} | {@link Playlist}
2867
+ * @param {PlayOptions} [options] Optional options
2868
+ * @throws {DisTubeError}
2869
+ * @example
2870
+ * client.on('message', (message) => {
2871
+ * if (!message.content.startsWith(config.prefix)) return;
2872
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
2873
+ * const command = args.shift();
2874
+ * if (command == "play")
2875
+ * distube.play(message.member.voice.channel, args.join(" "), {
2876
+ * member: message.member,
2877
+ * textChannel: message.channel,
2878
+ * message
2879
+ * });
2880
+ * });
2881
+ * @returns {Promise<void>}
2882
+ */
2234
2883
  async play(voiceChannel, song, options = {}) {
2235
2884
  if (!isSupportedVoiceChannel(voiceChannel)) {
2236
2885
  throw new DisTubeError("INVALID_TYPE", "BaseGuildVoiceChannel", voiceChannel, "voiceChannel");
@@ -2297,6 +2946,20 @@ ${e.message}`;
2297
2946
  queue?._taskQueue.resolve();
2298
2947
  }
2299
2948
  }
2949
+ /**
2950
+ * Create a custom playlist
2951
+ * @returns {Promise<Playlist>}
2952
+ * @param {Array<string|Song|SearchResult>} songs Array of url, Song or SearchResult
2953
+ * @param {CustomPlaylistOptions} [options] Optional options
2954
+ * @example
2955
+ * const songs = ["https://www.youtube.com/watch?v=xxx", "https://www.youtube.com/watch?v=yyy"];
2956
+ * const playlist = await distube.createCustomPlaylist(songs, {
2957
+ * member: message.member,
2958
+ * properties: { name: "My playlist name", source: "custom" },
2959
+ * parallel: true
2960
+ * });
2961
+ * distube.play(voiceChannel, playlist, { ... });
2962
+ */
2300
2963
  async createCustomPlaylist(songs, options = {}) {
2301
2964
  const { member, properties, parallel, metadata } = { parallel: true, ...options };
2302
2965
  if (!Array.isArray(songs))
@@ -2311,8 +2974,6 @@ ${e.message}`;
2311
2974
  if (member && !isMemberInstance(member)) {
2312
2975
  throw new DisTubeError("INVALID_TYPE", "Discord.Member", member, "options.member");
2313
2976
  }
2314
- if (!filteredSongs.length)
2315
- throw new DisTubeError("NO_VALID_SONG");
2316
2977
  let resolvedSongs;
2317
2978
  if (parallel) {
2318
2979
  const promises = filteredSongs.map(
@@ -2328,6 +2989,18 @@ ${e.message}`;
2328
2989
  }
2329
2990
  return new Playlist(resolvedSongs, { member, properties, metadata });
2330
2991
  }
2992
+ /**
2993
+ * Search for a song. You can customize how user answers instead of send a number.
2994
+ * Then use {@link DisTube#play} to play it.
2995
+ *
2996
+ * @param {string} string The string search for
2997
+ * @param {Object} options Search options
2998
+ * @param {number} [options.limit=10] Limit the results
2999
+ * @param {SearchResultType} [options.type=SearchResultType.VIDEO] Type of results (`video` or `playlist`).
3000
+ * @param {boolean} [options.safeSearch=false] Whether or not use safe search (YouTube restricted mode)
3001
+ * @throws {Error}
3002
+ * @returns {Promise<Array<SearchResult>>} Array of results
3003
+ */
2331
3004
  async search(string, options = {}) {
2332
3005
  const opts = { type: "video" /* VIDEO */, limit: 10, safeSearch: false, ...options };
2333
3006
  if (typeof opts.type !== "string" || !["video", "playlist"].includes(opts.type)) {
@@ -2357,63 +3030,234 @@ ${e.message}`;
2357
3030
  return this.search(string, options);
2358
3031
  }
2359
3032
  }
3033
+ /**
3034
+ * Get the guild queue
3035
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3036
+ * @returns {Queue?}
3037
+ * @throws {Error}
3038
+ * @example
3039
+ * client.on('message', (message) => {
3040
+ * if (!message.content.startsWith(config.prefix)) return;
3041
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3042
+ * const command = args.shift();
3043
+ * if (command == "queue") {
3044
+ * const queue = distube.getQueue(message);
3045
+ * message.channel.send('Current queue:\n' + queue.songs.map((song, id) =>
3046
+ * `**${id+1}**. [${song.name}](${song.url}) - \`${song.formattedDuration}\``
3047
+ * ).join("\n"));
3048
+ * }
3049
+ * });
3050
+ */
2360
3051
  getQueue(guild) {
2361
3052
  return this.queues.get(guild);
2362
3053
  }
3054
+ /**
3055
+ * Pause the guild stream
3056
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3057
+ * @returns {Queue} The guild queue
3058
+ * @throws {Error}
3059
+ */
2363
3060
  pause(guild) {
2364
3061
  const q = this.getQueue(guild);
2365
3062
  if (!q)
2366
3063
  throw new DisTubeError("NO_QUEUE");
2367
3064
  return q.pause();
2368
3065
  }
3066
+ /**
3067
+ * Resume the guild stream
3068
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3069
+ * @returns {Queue} The guild queue
3070
+ * @throws {Error}
3071
+ */
2369
3072
  resume(guild) {
2370
3073
  const q = this.getQueue(guild);
2371
3074
  if (!q)
2372
3075
  throw new DisTubeError("NO_QUEUE");
2373
3076
  return q.resume();
2374
3077
  }
3078
+ /**
3079
+ * Stop the guild stream
3080
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3081
+ * @returns {Promise<void>}
3082
+ * @throws {Error}
3083
+ * @example
3084
+ * client.on('message', (message) => {
3085
+ * if (!message.content.startsWith(config.prefix)) return;
3086
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3087
+ * const command = args.shift();
3088
+ * if (command == "stop") {
3089
+ * distube.stop(message);
3090
+ * message.channel.send("Stopped the queue!");
3091
+ * }
3092
+ * });
3093
+ */
2375
3094
  stop(guild) {
2376
3095
  const q = this.getQueue(guild);
2377
3096
  if (!q)
2378
3097
  throw new DisTubeError("NO_QUEUE");
2379
3098
  return q.stop();
2380
3099
  }
3100
+ /**
3101
+ * Set the guild stream's volume
3102
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3103
+ * @param {number} percent The percentage of volume you want to set
3104
+ * @returns {Queue} The guild queue
3105
+ * @throws {Error}
3106
+ * @example
3107
+ * client.on('message', (message) => {
3108
+ * if (!message.content.startsWith(config.prefix)) return;
3109
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3110
+ * const command = args.shift();
3111
+ * if (command == "volume")
3112
+ * distube.setVolume(message, Number(args[0]));
3113
+ * });
3114
+ */
2381
3115
  setVolume(guild, percent) {
2382
3116
  const q = this.getQueue(guild);
2383
3117
  if (!q)
2384
3118
  throw new DisTubeError("NO_QUEUE");
2385
3119
  return q.setVolume(percent);
2386
3120
  }
3121
+ /**
3122
+ * Skip the playing song if there is a next song in the queue.
3123
+ * <info>If {@link Queue#autoplay} is `true` and there is no up next song,
3124
+ * DisTube will add and play a related song.</info>
3125
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3126
+ * @returns {Promise<Song>} The new Song will be played
3127
+ * @throws {Error}
3128
+ * @example
3129
+ * client.on('message', (message) => {
3130
+ * if (!message.content.startsWith(config.prefix)) return;
3131
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3132
+ * const command = args.shift();
3133
+ * if (command == "skip")
3134
+ * distube.skip(message);
3135
+ * });
3136
+ */
2387
3137
  skip(guild) {
2388
3138
  const q = this.getQueue(guild);
2389
3139
  if (!q)
2390
3140
  throw new DisTubeError("NO_QUEUE");
2391
3141
  return q.skip();
2392
3142
  }
3143
+ /**
3144
+ * Play the previous song
3145
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3146
+ * @returns {Promise<Song>} The new Song will be played
3147
+ * @throws {Error}
3148
+ * @example
3149
+ * client.on('message', (message) => {
3150
+ * if (!message.content.startsWith(config.prefix)) return;
3151
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3152
+ * const command = args.shift();
3153
+ * if (command == "previous")
3154
+ * distube.previous(message);
3155
+ * });
3156
+ */
2393
3157
  previous(guild) {
2394
3158
  const q = this.getQueue(guild);
2395
3159
  if (!q)
2396
3160
  throw new DisTubeError("NO_QUEUE");
2397
3161
  return q.previous();
2398
3162
  }
3163
+ /**
3164
+ * Shuffle the guild queue songs
3165
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3166
+ * @returns {Promise<Queue>} The guild queue
3167
+ * @example
3168
+ * client.on('message', (message) => {
3169
+ * if (!message.content.startsWith(config.prefix)) return;
3170
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3171
+ * const command = args.shift();
3172
+ * if (command == "shuffle")
3173
+ * distube.shuffle(message);
3174
+ * });
3175
+ */
2399
3176
  shuffle(guild) {
2400
3177
  const q = this.getQueue(guild);
2401
3178
  if (!q)
2402
3179
  throw new DisTubeError("NO_QUEUE");
2403
3180
  return q.shuffle();
2404
3181
  }
3182
+ /**
3183
+ * Jump to the song number in the queue.
3184
+ * The next one is 1, 2,...
3185
+ * The previous one is -1, -2,...
3186
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3187
+ * @param {number} num The song number to play
3188
+ * @returns {Promise<Song>} The new Song will be played
3189
+ * @throws {Error} if `num` is invalid number (0 < num < {@link Queue#songs}.length)
3190
+ * @example
3191
+ * client.on('message', (message) => {
3192
+ * if (!message.content.startsWith(config.prefix)) return;
3193
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3194
+ * const command = args.shift();
3195
+ * if (command == "jump")
3196
+ * distube.jump(message, parseInt(args[0]))
3197
+ * .catch(err => message.channel.send("Invalid song number."));
3198
+ * });
3199
+ */
2405
3200
  jump(guild, num) {
2406
3201
  const q = this.getQueue(guild);
2407
3202
  if (!q)
2408
3203
  throw new DisTubeError("NO_QUEUE");
2409
3204
  return q.jump(num);
2410
3205
  }
3206
+ /**
3207
+ * Set the repeat mode of the guild queue.\
3208
+ * Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
3209
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3210
+ * @param {RepeatMode?} [mode] The repeat modes (toggle if `undefined`)
3211
+ * @returns {RepeatMode} The new repeat mode
3212
+ * @example
3213
+ * client.on('message', (message) => {
3214
+ * if (!message.content.startsWith(config.prefix)) return;
3215
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3216
+ * const command = args.shift();
3217
+ * if (command == "repeat") {
3218
+ * let mode = distube.setRepeatMode(message, parseInt(args[0]));
3219
+ * mode = mode ? mode == 2 ? "Repeat queue" : "Repeat song" : "Off";
3220
+ * message.channel.send("Set repeat mode to `" + mode + "`");
3221
+ * }
3222
+ * });
3223
+ * @example
3224
+ * const { RepeatMode } = require("distube");
3225
+ * let mode;
3226
+ * switch(distube.setRepeatMode(message, parseInt(args[0]))) {
3227
+ * case RepeatMode.DISABLED:
3228
+ * mode = "Off";
3229
+ * break;
3230
+ * case RepeatMode.SONG:
3231
+ * mode = "Repeat a song";
3232
+ * break;
3233
+ * case RepeatMode.QUEUE:
3234
+ * mode = "Repeat all queue";
3235
+ * break;
3236
+ * }
3237
+ * message.channel.send("Set repeat mode to `" + mode + "`");
3238
+ */
2411
3239
  setRepeatMode(guild, mode) {
2412
3240
  const q = this.getQueue(guild);
2413
3241
  if (!q)
2414
3242
  throw new DisTubeError("NO_QUEUE");
2415
3243
  return q.setRepeatMode(mode);
2416
3244
  }
3245
+ /**
3246
+ * Toggle autoplay mode
3247
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3248
+ * @returns {boolean} Autoplay mode state
3249
+ * @throws {Error}
3250
+ * @example
3251
+ * client.on('message', (message) => {
3252
+ * if (!message.content.startsWith(config.prefix)) return;
3253
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3254
+ * const command = args.shift();
3255
+ * if (command == "autoplay") {
3256
+ * const mode = distube.toggleAutoplay(message);
3257
+ * message.channel.send("Set autoplay mode to `" + (mode ? "On" : "Off") + "`");
3258
+ * }
3259
+ * });
3260
+ */
2417
3261
  toggleAutoplay(guild) {
2418
3262
  const q = this.getQueue(guild);
2419
3263
  if (!q)
@@ -2421,18 +3265,43 @@ ${e.message}`;
2421
3265
  q.autoplay = !q.autoplay;
2422
3266
  return q.autoplay;
2423
3267
  }
3268
+ /**
3269
+ * Add related song to the queue
3270
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3271
+ * @returns {Promise<Song>} The guild queue
3272
+ */
2424
3273
  addRelatedSong(guild) {
2425
3274
  const q = this.getQueue(guild);
2426
3275
  if (!q)
2427
3276
  throw new DisTubeError("NO_QUEUE");
2428
3277
  return q.addRelatedSong();
2429
3278
  }
3279
+ /**
3280
+ * Set the playing time to another position
3281
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3282
+ * @param {number} time Time in seconds
3283
+ * @returns {Queue} Seeked queue
3284
+ * @example
3285
+ * client.on('message', message => {
3286
+ * if (!message.content.startsWith(config.prefix)) return;
3287
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3288
+ * const command = args.shift();
3289
+ * if (command = 'seek')
3290
+ * distube.seek(message, Number(args[0]));
3291
+ * });
3292
+ */
2430
3293
  seek(guild, time) {
2431
3294
  const q = this.getQueue(guild);
2432
3295
  if (!q)
2433
3296
  throw new DisTubeError("NO_QUEUE");
2434
3297
  return q.seek(time);
2435
3298
  }
3299
+ /**
3300
+ * Emit error event
3301
+ * @param {Error} error error
3302
+ * @param {Discord.BaseGuildTextChannel} [channel] Text channel where the error is encountered.
3303
+ * @private
3304
+ */
2436
3305
  emitError(error, channel) {
2437
3306
  if (this.listeners("error").length) {
2438
3307
  this.emit("error", channel, error);
@@ -2445,7 +3314,8 @@ ${e.message}`;
2445
3314
  }
2446
3315
  }
2447
3316
  };
2448
- __name(DisTube, "DisTube");
3317
+ __name(_DisTube, "DisTube");
3318
+ var DisTube = _DisTube;
2449
3319
  // Annotate the CommonJS export names for ESM import in node:
2450
3320
  0 && (module.exports = {
2451
3321
  BaseManager,
@@ -2485,6 +3355,7 @@ __name(DisTube, "DisTube");
2485
3355
  isGuildInstance,
2486
3356
  isMemberInstance,
2487
3357
  isMessageInstance,
3358
+ isNsfwChannel,
2488
3359
  isObject,
2489
3360
  isRecord,
2490
3361
  isSnowflake,