distube 4.0.4 → 4.0.6

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.6",
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.17",
123
128
  "@distube/ytpl": "^1.1.1",
124
- "@distube/ytsr": "^1.1.8",
125
- "prism-media": "https://codeload.github.com/distubejs/prism-media/tar.gz/main#workaround.tar.gz",
129
+ "@distube/ytsr": "^1.1.9",
130
+ "prism-media": "npm:@distube/prism-media@latest",
126
131
  "tiny-typed-emitter": "^2.1.0",
127
- tslib: "^2.4.0",
128
- undici: "^5.8.0"
132
+ tslib: "^2.6.1",
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.5",
148
+ "@typescript-eslint/eslint-plugin": "^6.2.0",
149
+ "@typescript-eslint/parser": "^6.2.0",
150
+ "babel-jest": "^29.6.2",
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.9.0",
155
+ "eslint-plugin-deprecation": "^1.5.0",
156
+ "eslint-plugin-jsdoc": "^46.4.5",
157
+ husky: "^8.0.3",
158
+ jest: "^29.6.2",
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.17",
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,104 @@ 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
+ if (typeof options.seek === "number" && options.seek > 0)
1163
+ args.unshift("-ss", options.seek.toString());
1164
+ if (Array.isArray(options.ffmpegArgs) && options.ffmpegArgs.length)
1165
+ args.push(...options.ffmpegArgs);
1166
+ this.stream = new import_prism_media.FFmpeg({ args, shell: false });
1167
+ this.stream._readableState && (this.stream._readableState.highWaterMark = 1 << 25);
1168
+ }
1169
+ /**
1170
+ * Create a stream from ytdl video formats
1171
+ * @param {ytdl.videoFormat[]} formats ytdl video formats
1172
+ * @param {StreamOptions} options options
1173
+ * @returns {DisTubeStream}
1174
+ * @private
1175
+ */
1176
+ static YouTube(formats, options = {}) {
1177
+ if (!formats || !formats.length)
1178
+ throw new DisTubeError("UNAVAILABLE_VIDEO");
1179
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
1180
+ throw new DisTubeError("INVALID_TYPE", "object", options, "options");
1181
+ }
1182
+ const bestFormat = chooseBestVideoFormat(formats, options.isLive);
1183
+ if (!bestFormat)
1184
+ throw new DisTubeError("UNPLAYABLE_FORMATS");
1185
+ return new _DisTubeStream(bestFormat.url, options);
1186
+ }
1187
+ /**
1188
+ * Create a stream from a stream url
1189
+ * @param {string} url stream url
1190
+ * @param {StreamOptions} options options
1191
+ * @returns {DisTubeStream}
1192
+ * @private
1193
+ */
1194
+ static DirectLink(url, options = {}) {
1195
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
1196
+ throw new DisTubeError("INVALID_TYPE", "object", options, "options");
1197
+ }
1198
+ if (typeof url !== "string" || !isURL(url)) {
1199
+ throw new DisTubeError("INVALID_TYPE", "an URL", url);
1200
+ }
1201
+ return new _DisTubeStream(url, options);
1202
+ }
1203
+ };
1204
+ __name(_DisTubeStream, "DisTubeStream");
1205
+ var DisTubeStream = _DisTubeStream;
925
1206
 
926
1207
  // src/core/DisTubeHandler.ts
927
1208
  var import_ytpl = __toESM(require("@distube/ytpl"));
928
1209
  var import_ytdl_core = __toESM(require("@distube/ytdl-core"));
929
- var DisTubeHandler = class extends DisTubeBase {
1210
+ var _DisTubeHandler = class _DisTubeHandler extends DisTubeBase {
930
1211
  constructor(distube) {
931
1212
  super(distube);
932
1213
  const client = this.client;
@@ -976,11 +1257,23 @@ var DisTubeHandler = class extends DisTubeBase {
976
1257
  }
977
1258
  return options;
978
1259
  }
1260
+ /**
1261
+ * @param {string} url url
1262
+ * @param {boolean} [basic=false] getBasicInfo?
1263
+ * @returns {Promise<ytdl.videoInfo>}
1264
+ */
979
1265
  getYouTubeInfo(url, basic = false) {
980
1266
  if (basic)
981
1267
  return import_ytdl_core.default.getBasicInfo(url, this.ytdlOptions);
982
1268
  return import_ytdl_core.default.getInfo(url, this.ytdlOptions);
983
1269
  }
1270
+ /**
1271
+ * Resolve a url or a supported object to a {@link Song} or {@link Playlist}
1272
+ * @param {string|Song|SearchResult|Playlist} song URL | {@link Song}| {@link SearchResult} | {@link Playlist}
1273
+ * @param {ResolveOptions} [options] Optional options
1274
+ * @returns {Promise<Song|Playlist|null>} Resolved
1275
+ * @throws {DisTubeError}
1276
+ */
984
1277
  async resolve(song, options = {}) {
985
1278
  if (song instanceof Song || song instanceof Playlist) {
986
1279
  if ("metadata" in options)
@@ -1011,6 +1304,12 @@ var DisTubeHandler = class extends DisTubeBase {
1011
1304
  }
1012
1305
  throw new DisTubeError("CANNOT_RESOLVE_SONG", song);
1013
1306
  }
1307
+ /**
1308
+ * Resolve Song[] or YouTube playlist url to a Playlist
1309
+ * @param {Playlist|Song[]|string} playlist Resolvable playlist
1310
+ * @param {ResolvePlaylistOptions} options Optional options
1311
+ * @returns {Promise<Playlist>}
1312
+ */
1014
1313
  async resolvePlaylist(playlist, options = {}) {
1015
1314
  const { member, source, metadata } = { source: "youtube", ...options };
1016
1315
  if (playlist instanceof Playlist) {
@@ -1037,6 +1336,13 @@ var DisTubeHandler = class extends DisTubeBase {
1037
1336
  }
1038
1337
  return new Playlist(playlist, { member, properties: { source }, metadata });
1039
1338
  }
1339
+ /**
1340
+ * Search for a song, fire {@link DisTube#event:error} if not found.
1341
+ * @param {Discord.Message} message The original message from an user
1342
+ * @param {string} query The query string
1343
+ * @returns {Promise<SearchResult?>} Song info
1344
+ * @throws {DisTubeError}
1345
+ */
1040
1346
  async searchSong(message, query) {
1041
1347
  if (!isMessageInstance(message))
1042
1348
  throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
@@ -1047,7 +1353,7 @@ var DisTubeHandler = class extends DisTubeBase {
1047
1353
  const limit = this.options.searchSongs > 1 ? this.options.searchSongs : 1;
1048
1354
  const results = await this.distube.search(query, {
1049
1355
  limit,
1050
- safeSearch: this.options.nsfw ? false : !message.channel?.nsfw
1356
+ safeSearch: this.options.nsfw ? false : !isNsfwChannel(message.channel)
1051
1357
  }).catch(() => {
1052
1358
  if (!this.emit("searchNoResult", message, query)) {
1053
1359
  console.warn("searchNoResult event does not have any listeners! Emits `error` event instead.");
@@ -1058,6 +1364,17 @@ var DisTubeHandler = class extends DisTubeBase {
1058
1364
  return null;
1059
1365
  return this.createSearchMessageCollector(message, results, query);
1060
1366
  }
1367
+ /**
1368
+ * Create a message collector for selecting search results.
1369
+ *
1370
+ * Needed events: {@link DisTube#event:searchResult}, {@link DisTube#event:searchCancel},
1371
+ * {@link DisTube#event:searchInvalidAnswer}, {@link DisTube#event:searchDone}.
1372
+ * @param {Discord.Message} message The original message from an user
1373
+ * @param {Array<SearchResult|Song|Playlist>} results The search results
1374
+ * @param {string?} [query] The query string
1375
+ * @returns {Promise<SearchResult|Song|Playlist|null>} Selected result
1376
+ * @throws {DisTubeError}
1377
+ */
1061
1378
  async createSearchMessageCollector(message, results, query) {
1062
1379
  if (!isMessageInstance(message))
1063
1380
  throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
@@ -1110,19 +1427,26 @@ var DisTubeHandler = class extends DisTubeBase {
1110
1427
  }
1111
1428
  return result;
1112
1429
  }
1430
+ /**
1431
+ * Play or add a {@link Playlist} to the queue.
1432
+ * @param {Discord.BaseGuildVoiceChannel} voiceChannel A voice channel
1433
+ * @param {Playlist|string} playlist A YouTube playlist url | a Playlist
1434
+ * @param {PlayHandlerOptions} [options] Optional options
1435
+ * @returns {Promise<void>}
1436
+ * @throws {DisTubeError}
1437
+ */
1113
1438
  async playPlaylist(voiceChannel, playlist, options = {}) {
1114
1439
  const { textChannel, skip } = { skip: false, ...options };
1115
1440
  const position = Number(options.position) || (skip ? 1 : 0);
1116
1441
  if (!(playlist instanceof Playlist))
1117
1442
  throw new DisTubeError("INVALID_TYPE", "Playlist", playlist, "playlist");
1118
1443
  const queue = this.queues.get(voiceChannel);
1119
- if (!this.options.nsfw && !(queue?.textChannel || textChannel)?.nsfw) {
1444
+ const isNsfw = isNsfwChannel(queue?.textChannel || textChannel);
1445
+ if (!this.options.nsfw && !isNsfw)
1120
1446
  playlist.songs = playlist.songs.filter((s) => !s.age_restricted);
1121
- }
1122
1447
  if (!playlist.songs.length) {
1123
- if (!this.options.nsfw && !textChannel?.nsfw) {
1448
+ if (!this.options.nsfw && !isNsfw)
1124
1449
  throw new DisTubeError("EMPTY_FILTERED_PLAYLIST");
1125
- }
1126
1450
  throw new DisTubeError("EMPTY_PLAYLIST");
1127
1451
  }
1128
1452
  if (queue) {
@@ -1142,13 +1466,21 @@ var DisTubeHandler = class extends DisTubeBase {
1142
1466
  }
1143
1467
  }
1144
1468
  }
1469
+ /**
1470
+ * Play or add a {@link Song} to the queue.
1471
+ * @param {Discord.BaseGuildVoiceChannel} voiceChannel A voice channel
1472
+ * @param {Song} song A YouTube playlist url | a Playlist
1473
+ * @param {PlayHandlerOptions} [options] Optional options
1474
+ * @returns {Promise<void>}
1475
+ * @throws {DisTubeError}
1476
+ */
1145
1477
  async playSong(voiceChannel, song, options = {}) {
1146
1478
  if (!(song instanceof Song))
1147
1479
  throw new DisTubeError("INVALID_TYPE", "Song", song, "song");
1148
1480
  const { textChannel, skip } = { skip: false, ...options };
1149
1481
  const position = Number(options.position) || (skip ? 1 : 0);
1150
1482
  const queue = this.queues.get(voiceChannel);
1151
- if (!this.options.nsfw && song.age_restricted && !(queue?.textChannel || textChannel)?.nsfw) {
1483
+ if (!this.options.nsfw && song.age_restricted && !isNsfwChannel(queue?.textChannel || textChannel)) {
1152
1484
  throw new DisTubeError("NON_NSFW");
1153
1485
  }
1154
1486
  if (queue) {
@@ -1168,12 +1500,35 @@ var DisTubeHandler = class extends DisTubeBase {
1168
1500
  }
1169
1501
  }
1170
1502
  }
1503
+ /**
1504
+ * Get {@link Song}'s stream info and attach it to the song.
1505
+ * @param {Song} song A Song
1506
+ */
1507
+ async attachStreamInfo(song) {
1508
+ const { url, source, formats, streamURL, isLive } = song;
1509
+ if (source === "youtube") {
1510
+ if (!formats || !chooseBestVideoFormat(formats, isLive)) {
1511
+ song._patchYouTube(await this.handler.getYouTubeInfo(url));
1512
+ }
1513
+ } else if (!streamURL) {
1514
+ for (const plugin of [...this.distube.extractorPlugins, ...this.distube.customPlugins]) {
1515
+ if (await plugin.validate(url)) {
1516
+ const info = [plugin.getStreamURL(url), plugin.getRelatedSongs(url)];
1517
+ const result = await Promise.all(info);
1518
+ song.streamURL = result[0];
1519
+ song.related = result[1];
1520
+ break;
1521
+ }
1522
+ }
1523
+ }
1524
+ }
1171
1525
  };
1172
- __name(DisTubeHandler, "DisTubeHandler");
1526
+ __name(_DisTubeHandler, "DisTubeHandler");
1527
+ var DisTubeHandler = _DisTubeHandler;
1173
1528
 
1174
1529
  // src/core/DisTubeOptions.ts
1175
1530
  var _validateOptions, validateOptions_fn;
1176
- var Options = class {
1531
+ var _Options = class _Options {
1177
1532
  constructor(options) {
1178
1533
  __privateAdd(this, _validateOptions);
1179
1534
  __publicField(this, "plugins");
@@ -1222,7 +1577,6 @@ var Options = class {
1222
1577
  __privateMethod(this, _validateOptions, validateOptions_fn).call(this);
1223
1578
  }
1224
1579
  };
1225
- __name(Options, "Options");
1226
1580
  _validateOptions = new WeakSet();
1227
1581
  validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
1228
1582
  if (typeof options.emitNewSongOnly !== "boolean") {
@@ -1303,95 +1657,34 @@ validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
1303
1657
  throw new DisTubeError("INVALID_TYPE", "boolean", options.directLink, "DisTubeOptions.directLink");
1304
1658
  }
1305
1659
  }, "#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");
1660
+ __name(_Options, "Options");
1661
+ var Options = _Options;
1379
1662
 
1380
1663
  // src/core/manager/BaseManager.ts
1381
1664
  var import_discord2 = require("discord.js");
1382
- var BaseManager = class extends DisTubeBase {
1665
+ var _BaseManager = class _BaseManager extends DisTubeBase {
1383
1666
  constructor() {
1384
1667
  super(...arguments);
1668
+ /**
1669
+ * The collection of items for this manager.
1670
+ * @type {Collection}
1671
+ * @name BaseManager#collection
1672
+ */
1385
1673
  __publicField(this, "collection", new import_discord2.Collection());
1386
1674
  }
1675
+ /**
1676
+ * The size of the collection.
1677
+ * @type {number}
1678
+ */
1387
1679
  get size() {
1388
1680
  return this.collection.size;
1389
1681
  }
1390
1682
  };
1391
- __name(BaseManager, "BaseManager");
1683
+ __name(_BaseManager, "BaseManager");
1684
+ var BaseManager = _BaseManager;
1392
1685
 
1393
1686
  // src/core/manager/GuildIdManager.ts
1394
- var GuildIdManager = class extends BaseManager {
1687
+ var _GuildIdManager = class _GuildIdManager extends BaseManager {
1395
1688
  add(idOrInstance, data) {
1396
1689
  const id = resolveGuildId(idOrInstance);
1397
1690
  const existing = this.get(id);
@@ -1409,11 +1702,30 @@ var GuildIdManager = class extends BaseManager {
1409
1702
  return this.collection.has(resolveGuildId(idOrInstance));
1410
1703
  }
1411
1704
  };
1412
- __name(GuildIdManager, "GuildIdManager");
1705
+ __name(_GuildIdManager, "GuildIdManager");
1706
+ var GuildIdManager = _GuildIdManager;
1413
1707
 
1414
1708
  // src/core/manager/DisTubeVoiceManager.ts
1415
1709
  var import_voice3 = require("@discordjs/voice");
1416
- var DisTubeVoiceManager = class extends GuildIdManager {
1710
+ var _DisTubeVoiceManager = class _DisTubeVoiceManager extends GuildIdManager {
1711
+ /**
1712
+ * Get a {@link DisTubeVoice}.
1713
+ * @method get
1714
+ * @memberof DisTubeVoiceManager#
1715
+ * @param {GuildIdResolvable} guild The queue resolvable to resolve
1716
+ * @returns {DisTubeVoice?}
1717
+ */
1718
+ /**
1719
+ * Collection of {@link DisTubeVoice}.
1720
+ * @name DisTubeVoiceManager#collection
1721
+ * @type {Discord.Collection<string, DisTubeVoice>}
1722
+ */
1723
+ /**
1724
+ * Create a {@link DisTubeVoice}
1725
+ * @param {Discord.BaseGuildVoiceChannel} channel A voice channel to join
1726
+ * @returns {DisTubeVoice}
1727
+ * @private
1728
+ */
1417
1729
  create(channel) {
1418
1730
  const existing = this.get(channel.guildId);
1419
1731
  if (existing) {
@@ -1422,12 +1734,21 @@ var DisTubeVoiceManager = class extends GuildIdManager {
1422
1734
  }
1423
1735
  return new DisTubeVoice(this, channel);
1424
1736
  }
1737
+ /**
1738
+ * Join a voice channel
1739
+ * @param {Discord.BaseGuildVoiceChannel} channel A voice channel to join
1740
+ * @returns {Promise<DisTubeVoice>}
1741
+ */
1425
1742
  join(channel) {
1426
1743
  const existing = this.get(channel.guildId);
1427
1744
  if (existing)
1428
1745
  return existing.join(channel);
1429
1746
  return this.create(channel).join();
1430
1747
  }
1748
+ /**
1749
+ * Leave the connected voice channel in a guild
1750
+ * @param {GuildIdResolvable} guild Queue Resolvable
1751
+ */
1431
1752
  leave(guild) {
1432
1753
  const voice = this.get(guild);
1433
1754
  if (voice) {
@@ -1440,104 +1761,191 @@ var DisTubeVoiceManager = class extends GuildIdManager {
1440
1761
  }
1441
1762
  }
1442
1763
  };
1443
- __name(DisTubeVoiceManager, "DisTubeVoiceManager");
1764
+ __name(_DisTubeVoiceManager, "DisTubeVoiceManager");
1765
+ var DisTubeVoiceManager = _DisTubeVoiceManager;
1444
1766
 
1445
1767
  // 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 {
1768
+ var _resolve, resolve_fn, _apply, apply_fn, _removeFn, removeFn_fn;
1769
+ var _FilterManager = class _FilterManager extends BaseManager {
1448
1770
  constructor(queue) {
1449
1771
  super(queue.distube);
1450
- __privateAdd(this, _validate);
1451
- __privateAdd(this, _resolveName);
1452
- __privateAdd(this, _resolveValue);
1772
+ __privateAdd(this, _resolve);
1453
1773
  __privateAdd(this, _apply);
1774
+ __privateAdd(this, _removeFn);
1775
+ /**
1776
+ * Collection of {@link Filter}.
1777
+ * @name FilterManager#collection
1778
+ * @type {Discord.Collection<string, DisTubeVoice>}
1779
+ */
1454
1780
  __publicField(this, "queue");
1455
1781
  this.queue = queue;
1456
1782
  }
1783
+ /**
1784
+ * Enable a filter or multiple filters to the manager
1785
+ * @param {FilterResolvable|FilterResolvable[]} filterOrFilters The filter or filters to enable
1786
+ * @param {boolean} [override=false] Wether or not override the applied filter with new filter value
1787
+ * @returns {FilterManager}
1788
+ */
1457
1789
  add(filterOrFilters, override = false) {
1458
1790
  if (Array.isArray(filterOrFilters)) {
1459
- const resolvedFilters = filterOrFilters.map((f) => __privateMethod(this, _validate, validate_fn).call(this, f));
1460
- const newFilters = resolvedFilters.reduceRight((unique, o) => {
1461
- if (!unique.some((obj) => obj === o && obj.name === o) && !unique.some((obj) => obj !== o.name && obj.name !== o.name)) {
1462
- if (!this.has(o))
1463
- unique.push(o);
1464
- if (this.has(o) && override) {
1465
- this.remove(o);
1466
- unique.push(o);
1467
- }
1468
- }
1469
- return unique;
1470
- }, []).reverse();
1471
- return this.set([...this.collection.values(), ...newFilters]);
1791
+ for (const filter of filterOrFilters) {
1792
+ const f = __privateMethod(this, _resolve, resolve_fn).call(this, filter);
1793
+ if (override || !this.has(f))
1794
+ this.collection.set(f.name, f);
1795
+ }
1796
+ } else {
1797
+ const f = __privateMethod(this, _resolve, resolve_fn).call(this, filterOrFilters);
1798
+ if (override || !this.has(f))
1799
+ this.collection.set(f.name, f);
1472
1800
  }
1473
- return this.set([...this.collection.values(), filterOrFilters]);
1801
+ __privateMethod(this, _apply, apply_fn).call(this);
1802
+ return this;
1474
1803
  }
1804
+ /**
1805
+ * Clear enabled filters of the manager
1806
+ * @returns {FilterManager}
1807
+ */
1475
1808
  clear() {
1476
1809
  return this.set([]);
1477
1810
  }
1811
+ /**
1812
+ * Set the filters applied to the manager
1813
+ * @param {FilterResolvable[]} filters The filters to apply
1814
+ * @returns {FilterManager}
1815
+ */
1478
1816
  set(filters) {
1817
+ if (!Array.isArray(filters))
1818
+ throw new DisTubeError("INVALID_TYPE", "Array<FilterResolvable>", filters, "filters");
1479
1819
  this.collection.clear();
1480
- for (const filter of filters) {
1481
- const resolved = __privateMethod(this, _validate, validate_fn).call(this, filter);
1482
- this.collection.set(__privateMethod(this, _resolveName, resolveName_fn).call(this, resolved), resolved);
1820
+ for (const f of filters) {
1821
+ const filter = __privateMethod(this, _resolve, resolve_fn).call(this, f);
1822
+ this.collection.set(filter.name, filter);
1483
1823
  }
1484
1824
  __privateMethod(this, _apply, apply_fn).call(this);
1485
1825
  return this;
1486
1826
  }
1827
+ /**
1828
+ * Disable a filter or multiple filters
1829
+ * @param {FilterResolvable|FilterResolvable[]} filterOrFilters The filter or filters to disable
1830
+ * @returns {FilterManager}
1831
+ */
1487
1832
  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
1833
  if (Array.isArray(filterOrFilters))
1490
- filterOrFilters.map(remove);
1834
+ filterOrFilters.map((f) => __privateMethod(this, _removeFn, removeFn_fn).call(this, f));
1491
1835
  else
1492
- remove(filterOrFilters);
1836
+ __privateMethod(this, _removeFn, removeFn_fn).call(this, filterOrFilters);
1493
1837
  __privateMethod(this, _apply, apply_fn).call(this);
1494
1838
  return this;
1495
1839
  }
1840
+ /**
1841
+ * Check whether a filter enabled or not
1842
+ * @param {FilterResolvable} filter The filter to check
1843
+ * @returns {boolean}
1844
+ */
1496
1845
  has(filter) {
1497
- return this.collection.has(__privateMethod(this, _resolveName, resolveName_fn).call(this, filter));
1846
+ return this.collection.has(typeof filter === "string" ? filter : __privateMethod(this, _resolve, resolve_fn).call(this, filter).name);
1498
1847
  }
1848
+ /**
1849
+ * Array of enabled filter names
1850
+ * @type {Array<string>}
1851
+ * @readonly
1852
+ */
1499
1853
  get names() {
1500
- return this.collection.map((f) => __privateMethod(this, _resolveName, resolveName_fn).call(this, f));
1854
+ return [...this.collection.keys()];
1501
1855
  }
1856
+ /**
1857
+ * Array of enabled filters
1858
+ * @type {Array<Filter>}
1859
+ * @readonly
1860
+ */
1502
1861
  get values() {
1503
- return this.collection.map((f) => __privateMethod(this, _resolveValue, resolveValue_fn).call(this, f));
1862
+ return [...this.collection.values()];
1863
+ }
1864
+ get ffmpegArgs() {
1865
+ return this.size ? ["-af", this.values.map((f) => f.value).join(",")] : [];
1504
1866
  }
1505
1867
  toString() {
1506
1868
  return this.names.toString();
1507
1869
  }
1508
1870
  };
1509
- __name(FilterManager, "FilterManager");
1510
- _validate = new WeakSet();
1511
- validate_fn = /* @__PURE__ */ __name(function(filter) {
1512
- if (typeof filter === "string" && Object.prototype.hasOwnProperty.call(this.distube.filters, filter) || typeof filter === "object" && typeof filter.name === "string" && typeof filter.value === "string") {
1871
+ _resolve = new WeakSet();
1872
+ resolve_fn = /* @__PURE__ */ __name(function(filter) {
1873
+ if (typeof filter === "object" && typeof filter.name === "string" && typeof filter.value === "string") {
1513
1874
  return filter;
1514
1875
  }
1876
+ if (typeof filter === "string" && Object.prototype.hasOwnProperty.call(this.distube.filters, filter)) {
1877
+ return {
1878
+ name: filter,
1879
+ value: this.distube.filters[filter]
1880
+ };
1881
+ }
1515
1882
  throw new DisTubeError("INVALID_TYPE", "FilterResolvable", filter, "filter");
1516
- }, "#validate");
1517
- _resolveName = new WeakSet();
1518
- resolveName_fn = /* @__PURE__ */ __name(function(filter) {
1519
- return typeof filter === "string" ? filter : filter.name;
1520
- }, "#resolveName");
1521
- _resolveValue = new WeakSet();
1522
- resolveValue_fn = /* @__PURE__ */ __name(function(filter) {
1523
- return typeof filter === "string" ? this.distube.filters[filter] : filter.value;
1524
- }, "#resolveValue");
1883
+ }, "#resolve");
1525
1884
  _apply = new WeakSet();
1526
1885
  apply_fn = /* @__PURE__ */ __name(function() {
1527
1886
  this.queue.beginTime = this.queue.currentTime;
1528
1887
  this.queues.playSong(this.queue);
1529
1888
  }, "#apply");
1889
+ _removeFn = new WeakSet();
1890
+ removeFn_fn = /* @__PURE__ */ __name(function(f) {
1891
+ return this.collection.delete(__privateMethod(this, _resolve, resolve_fn).call(this, f).name);
1892
+ }, "#removeFn");
1893
+ __name(_FilterManager, "FilterManager");
1894
+ var FilterManager = _FilterManager;
1530
1895
 
1531
1896
  // 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 {
1897
+ var _voiceEventHandler, voiceEventHandler_fn, _emitPlaySong, emitPlaySong_fn, _handleSongFinish, handleSongFinish_fn, _handlePlayingError, handlePlayingError_fn;
1898
+ var _QueueManager = class _QueueManager extends GuildIdManager {
1534
1899
  constructor() {
1535
1900
  super(...arguments);
1901
+ /**
1902
+ * Get a Queue from this QueueManager.
1903
+ * @method get
1904
+ * @memberof QueueManager#
1905
+ * @param {GuildIdResolvable} guild Resolvable thing from a guild
1906
+ * @returns {Queue?}
1907
+ */
1908
+ /**
1909
+ * Listen to DisTubeVoice events and handle the Queue
1910
+ * @private
1911
+ * @param {Queue} queue Queue
1912
+ */
1536
1913
  __privateAdd(this, _voiceEventHandler);
1914
+ /**
1915
+ * Whether or not emit playSong event
1916
+ * @param {Queue} queue Queue
1917
+ * @private
1918
+ * @returns {boolean}
1919
+ */
1920
+ __privateAdd(this, _emitPlaySong);
1921
+ /**
1922
+ * Handle the queue when a Song finish
1923
+ * @private
1924
+ * @param {Queue} queue queue
1925
+ * @returns {Promise<void>}
1926
+ */
1537
1927
  __privateAdd(this, _handleSongFinish);
1928
+ /**
1929
+ * Handle error while playing
1930
+ * @private
1931
+ * @param {Queue} queue queue
1932
+ * @param {Error} error error
1933
+ */
1538
1934
  __privateAdd(this, _handlePlayingError);
1539
- __privateAdd(this, _emitPlaySong);
1540
1935
  }
1936
+ /**
1937
+ * Collection of {@link Queue}.
1938
+ * @name QueueManager#collection
1939
+ * @type {Discord.Collection<string, Queue>}
1940
+ */
1941
+ /**
1942
+ * Create a {@link Queue}
1943
+ * @private
1944
+ * @param {Discord.BaseGuildVoiceChannel} channel A voice channel
1945
+ * @param {Song|Song[]} song First song
1946
+ * @param {Discord.BaseGuildTextChannel} textChannel Default text channel
1947
+ * @returns {Promise<Queue|true>} Returns `true` if encounter an error
1948
+ */
1541
1949
  async create(channel, song, textChannel) {
1542
1950
  if (this.has(channel.guildId))
1543
1951
  throw new DisTubeError("QUEUE_EXIST");
@@ -1555,49 +1963,46 @@ var QueueManager = class extends GuildIdManager {
1555
1963
  queue._taskQueue.resolve();
1556
1964
  }
1557
1965
  }
1966
+ /**
1967
+ * Create a ytdl stream
1968
+ * @param {Queue} queue Queue
1969
+ * @returns {DisTubeStream}
1970
+ */
1558
1971
  createStream(queue) {
1559
1972
  const { duration, formats, isLive, source, streamURL } = queue.songs[0];
1560
- const ffmpegArgs = queue.filters.size ? ["-af", queue.filters.values.join(",")] : void 0;
1561
- const seek = duration ? queue.beginTime : void 0;
1562
- const streamOptions = { ffmpegArgs, seek, isLive, type: this.options.streamType };
1973
+ const streamOptions = {
1974
+ ffmpegArgs: queue.filters.ffmpegArgs,
1975
+ seek: duration ? queue.beginTime : void 0,
1976
+ isLive,
1977
+ type: this.options.streamType
1978
+ };
1563
1979
  if (source === "youtube")
1564
1980
  return DisTubeStream.YouTube(formats, streamOptions);
1565
1981
  return DisTubeStream.DirectLink(streamURL, streamOptions);
1566
1982
  }
1983
+ /**
1984
+ * Play a song on voice connection
1985
+ * @private
1986
+ * @param {Queue} queue The guild queue
1987
+ * @returns {Promise<boolean>} error?
1988
+ */
1567
1989
  async playSong(queue) {
1568
1990
  if (!queue)
1569
1991
  return true;
1570
- if (!queue.songs.length) {
1992
+ if (queue.stopped || !queue.songs.length) {
1571
1993
  queue.stop();
1572
1994
  return true;
1573
1995
  }
1574
- if (queue.stopped)
1575
- return false;
1576
1996
  try {
1577
1997
  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
- }
1998
+ await this.handler.attachStreamInfo(song);
1999
+ if (queue.stopped || !queue.songs.length) {
2000
+ queue.stop();
2001
+ return true;
1593
2002
  }
1594
2003
  const stream = this.createStream(queue);
1595
2004
  queue.voice.play(stream);
1596
2005
  song.streamURL = stream.url;
1597
- if (queue.stopped)
1598
- queue.stop();
1599
- else if (queue.paused)
1600
- queue.voice.pause();
1601
2006
  return false;
1602
2007
  } catch (e) {
1603
2008
  __privateMethod(this, _handlePlayingError, handlePlayingError_fn).call(this, queue, e);
@@ -1605,7 +2010,6 @@ var QueueManager = class extends GuildIdManager {
1605
2010
  }
1606
2011
  }
1607
2012
  };
1608
- __name(QueueManager, "QueueManager");
1609
2013
  _voiceEventHandler = new WeakSet();
1610
2014
  voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
1611
2015
  queue._listeners = {
@@ -1622,6 +2026,10 @@ voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
1622
2026
  queue.voice.on(event, queue._listeners[event]);
1623
2027
  }
1624
2028
  }, "#voiceEventHandler");
2029
+ _emitPlaySong = new WeakSet();
2030
+ emitPlaySong_fn = /* @__PURE__ */ __name(function(queue) {
2031
+ return !this.options.emitNewSongOnly || queue.repeatMode === 1 /* SONG */ && queue._next || queue.repeatMode !== 1 /* SONG */ && queue.songs[0]?.id !== queue.songs[1]?.id;
2032
+ }, "#emitPlaySong");
1625
2033
  _handleSongFinish = new WeakSet();
1626
2034
  handleSongFinish_fn = /* @__PURE__ */ __name(async function(queue) {
1627
2035
  this.emit("finishSong", queue, queue.songs[0]);
@@ -1695,14 +2103,19 @@ Name: ${song.name}`;
1695
2103
  queue.stop();
1696
2104
  }
1697
2105
  }, "#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");
2106
+ __name(_QueueManager, "QueueManager");
2107
+ var QueueManager = _QueueManager;
1702
2108
 
1703
2109
  // src/struct/Queue.ts
1704
2110
  var _filters;
1705
- var Queue = class extends DisTubeBase {
2111
+ var _Queue = class _Queue extends DisTubeBase {
2112
+ /**
2113
+ * Create a queue for the guild
2114
+ * @param {DisTube} distube DisTube
2115
+ * @param {DisTubeVoice} voice Voice connection
2116
+ * @param {Song|Song[]} song First song(s)
2117
+ * @param {Discord.BaseGuildTextChannel?} textChannel Default text channel
2118
+ */
1706
2119
  constructor(distube, voice, song, textChannel) {
1707
2120
  super(distube);
1708
2121
  __publicField(this, "id");
@@ -1741,24 +2154,58 @@ var Queue = class extends DisTubeBase {
1741
2154
  this._taskQueue = new TaskQueue();
1742
2155
  this._listeners = void 0;
1743
2156
  }
2157
+ /**
2158
+ * The client user as a `GuildMember` of this queue's guild
2159
+ * @type {Discord.GuildMember?}
2160
+ */
1744
2161
  get clientMember() {
1745
2162
  return this.voice.channel.guild.members.me ?? void 0;
1746
2163
  }
2164
+ /**
2165
+ * The filter manager of the queue
2166
+ * @type {FilterManager}
2167
+ * @readonly
2168
+ */
1747
2169
  get filters() {
1748
2170
  return __privateGet(this, _filters);
1749
2171
  }
2172
+ /**
2173
+ * Formatted duration string.
2174
+ * @type {string}
2175
+ * @readonly
2176
+ */
1750
2177
  get formattedDuration() {
1751
2178
  return formatDuration(this.duration);
1752
2179
  }
2180
+ /**
2181
+ * Queue's duration.
2182
+ * @type {number}
2183
+ * @readonly
2184
+ */
1753
2185
  get duration() {
1754
2186
  return this.songs.length ? this.songs.reduce((prev, next) => prev + next.duration, 0) : 0;
1755
2187
  }
2188
+ /**
2189
+ * What time in the song is playing (in seconds).
2190
+ * @type {number}
2191
+ * @readonly
2192
+ */
1756
2193
  get currentTime() {
1757
2194
  return this.voice.playbackDuration + this.beginTime;
1758
2195
  }
2196
+ /**
2197
+ * Formatted {@link Queue#currentTime} string.
2198
+ * @type {string}
2199
+ * @readonly
2200
+ */
1759
2201
  get formattedCurrentTime() {
1760
2202
  return formatDuration(this.currentTime);
1761
2203
  }
2204
+ /**
2205
+ * The voice channel playing in.
2206
+ * @type {Discord.VoiceChannel|Discord.StageChannel|null}
2207
+ * @readonly
2208
+ */
1762
2209
  get voiceChannel() {
1763
2210
  return this.clientMember?.voice?.channel ?? null;
1764
2211
  }
@@ -1768,6 +2215,14 @@ var Queue = class extends DisTubeBase {
1768
2215
  set volume(value) {
1769
2216
  this.voice.volume = value;
1770
2217
  }
2218
+ /**
2219
+ * @private
2220
+ * Add a Song or an array of Song to the queue
2221
+ * @param {Song|Song[]} song Song to add
2222
+ * @param {number} [position=0] Position to add, <= 0 to add to the end of the queue
2223
+ * @throws {Error}
2224
+ * @returns {Queue} The guild queue
2225
+ */
1771
2226
  addToQueue(song, position = 0) {
1772
2227
  if (!song || Array.isArray(song) && !song.length) {
1773
2228
  throw new DisTubeError("INVALID_TYPE", ["Song", "Array<Song>"], song, "song");
@@ -1791,6 +2246,10 @@ var Queue = class extends DisTubeBase {
1791
2246
  delete song.formats;
1792
2247
  return this;
1793
2248
  }
2249
+ /**
2250
+ * Pause the guild stream
2251
+ * @returns {Queue} The guild queue
2252
+ */
1794
2253
  pause() {
1795
2254
  if (this.paused)
1796
2255
  throw new DisTubeError("PAUSED");
@@ -1799,6 +2258,10 @@ var Queue = class extends DisTubeBase {
1799
2258
  this.voice.pause();
1800
2259
  return this;
1801
2260
  }
2261
+ /**
2262
+ * Resume the guild stream
2263
+ * @returns {Queue} The guild queue
2264
+ */
1802
2265
  resume() {
1803
2266
  if (this.playing)
1804
2267
  throw new DisTubeError("RESUMED");
@@ -1807,10 +2270,22 @@ var Queue = class extends DisTubeBase {
1807
2270
  this.voice.unpause();
1808
2271
  return this;
1809
2272
  }
2273
+ /**
2274
+ * Set the guild stream's volume
2275
+ * @param {number} percent The percentage of volume you want to set
2276
+ * @returns {Queue} The guild queue
2277
+ */
1810
2278
  setVolume(percent) {
1811
2279
  this.volume = percent;
1812
2280
  return this;
1813
2281
  }
2282
+ /**
2283
+ * Skip the playing song if there is a next song in the queue.
2284
+ * <info>If {@link Queue#autoplay} is `true` and there is no up next song,
2285
+ * DisTube will add and play a related song.</info>
2286
+ * @returns {Promise<Song>} The song will skip to
2287
+ * @throws {Error}
2288
+ */
1814
2289
  async skip() {
1815
2290
  await this._taskQueue.queuing();
1816
2291
  try {
@@ -1828,6 +2303,11 @@ var Queue = class extends DisTubeBase {
1828
2303
  this._taskQueue.resolve();
1829
2304
  }
1830
2305
  }
2306
+ /**
2307
+ * Play the previous song if exists
2308
+ * @returns {Promise<Song>} The guild queue
2309
+ * @throws {Error}
2310
+ */
1831
2311
  async previous() {
1832
2312
  await this._taskQueue.queuing();
1833
2313
  try {
@@ -1844,6 +2324,10 @@ var Queue = class extends DisTubeBase {
1844
2324
  this._taskQueue.resolve();
1845
2325
  }
1846
2326
  }
2327
+ /**
2328
+ * Shuffle the queue's songs
2329
+ * @returns {Promise<Queue>} The guild queue
2330
+ */
1847
2331
  async shuffle() {
1848
2332
  await this._taskQueue.queuing();
1849
2333
  try {
@@ -1860,6 +2344,14 @@ var Queue = class extends DisTubeBase {
1860
2344
  this._taskQueue.resolve();
1861
2345
  }
1862
2346
  }
2347
+ /**
2348
+ * Jump to the song position in the queue.
2349
+ * The next one is 1, 2,...
2350
+ * The previous one is -1, -2,...
2351
+ * @param {number} position The song position to play
2352
+ * @returns {Promise<Song>} The new Song will be played
2353
+ * @throws {Error} if `num` is invalid number
2354
+ */
1863
2355
  async jump(position) {
1864
2356
  await this._taskQueue.queuing();
1865
2357
  try {
@@ -1893,6 +2385,12 @@ var Queue = class extends DisTubeBase {
1893
2385
  this._taskQueue.resolve();
1894
2386
  }
1895
2387
  }
2388
+ /**
2389
+ * Set the repeat mode of the guild queue.\
2390
+ * Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
2391
+ * @param {RepeatMode?} [mode] The repeat modes (toggle if `undefined`)
2392
+ * @returns {RepeatMode} The new repeat mode
2393
+ */
1896
2394
  setRepeatMode(mode) {
1897
2395
  if (mode !== void 0 && !Object.values(RepeatMode).includes(mode)) {
1898
2396
  throw new DisTubeError("INVALID_TYPE", ["RepeatMode", "undefined"], mode, "mode");
@@ -1905,6 +2403,11 @@ var Queue = class extends DisTubeBase {
1905
2403
  this.repeatMode = mode;
1906
2404
  return this.repeatMode;
1907
2405
  }
2406
+ /**
2407
+ * Set the playing time to another position
2408
+ * @param {number} time Time in seconds
2409
+ * @returns {Queue} The guild queue
2410
+ */
1908
2411
  seek(time) {
1909
2412
  if (typeof time !== "number")
1910
2413
  throw new DisTubeError("INVALID_TYPE", "number", time, "time");
@@ -1914,6 +2417,11 @@ var Queue = class extends DisTubeBase {
1914
2417
  this.queues.playSong(this);
1915
2418
  return this;
1916
2419
  }
2420
+ /**
2421
+ * Add a related song of the playing song to the queue
2422
+ * @returns {Promise<Song>} The added song
2423
+ * @throws {Error}
2424
+ */
1917
2425
  async addRelatedSong() {
1918
2426
  if (!this.songs?.[0])
1919
2427
  throw new DisTubeError("NO_PLAYING");
@@ -1926,6 +2434,9 @@ var Queue = class extends DisTubeBase {
1926
2434
  this.addToQueue(song);
1927
2435
  return song;
1928
2436
  }
2437
+ /**
2438
+ * Stop the guild stream and delete the queue
2439
+ */
1929
2440
  async stop() {
1930
2441
  await this._taskQueue.queuing();
1931
2442
  try {
@@ -1941,6 +2452,11 @@ var Queue = class extends DisTubeBase {
1941
2452
  this._taskQueue.resolve();
1942
2453
  }
1943
2454
  }
2455
+ /**
2456
+ * Remove the queue from the manager
2457
+ * (This does not leave the voice channel even if {@link DisTubeOptions|DisTubeOptions.leaveOnStop} is enabled)
2458
+ * @private
2459
+ */
1944
2460
  remove() {
1945
2461
  this.stopped = true;
1946
2462
  this.songs = [];
@@ -1953,72 +2469,138 @@ var Queue = class extends DisTubeBase {
1953
2469
  this.queues.remove(this.id);
1954
2470
  this.emit("deleteQueue", this);
1955
2471
  }
2472
+ /**
2473
+ * Toggle autoplay mode
2474
+ * @returns {boolean} Autoplay mode state
2475
+ */
1956
2476
  toggleAutoplay() {
1957
2477
  this.autoplay = !this.autoplay;
1958
2478
  return this.autoplay;
1959
2479
  }
1960
2480
  };
1961
- __name(Queue, "Queue");
1962
2481
  _filters = new WeakMap();
2482
+ __name(_Queue, "Queue");
2483
+ var Queue = _Queue;
1963
2484
 
1964
2485
  // src/struct/Plugin.ts
1965
- var Plugin = class {
2486
+ var _Plugin = class _Plugin {
1966
2487
  constructor() {
1967
2488
  __publicField(this, "distube");
1968
2489
  }
1969
2490
  init(distube) {
1970
2491
  this.distube = distube;
1971
2492
  }
2493
+ /**
2494
+ * Type of the plugin
2495
+ * @name Plugin#type
2496
+ * @type {PluginType}
2497
+ */
2498
+ /**
2499
+ * Emit an event to the {@link DisTube} class
2500
+ * @param {string} eventName Event name
2501
+ * @param {...any} args arguments
2502
+ * @returns {boolean}
2503
+ */
1972
2504
  emit(eventName, ...args) {
1973
2505
  return this.distube.emit(eventName, ...args);
1974
2506
  }
2507
+ /**
2508
+ * Emit error event to the {@link DisTube} class
2509
+ * @param {Error} error error
2510
+ * @param {Discord.BaseGuildTextChannel} [channel] Text channel where the error is encountered.
2511
+ */
1975
2512
  emitError(error, channel) {
1976
2513
  this.distube.emitError(error, channel);
1977
2514
  }
2515
+ /**
2516
+ * The queue manager
2517
+ * @type {QueueManager}
2518
+ * @readonly
2519
+ */
1978
2520
  get queues() {
1979
2521
  return this.distube.queues;
1980
2522
  }
2523
+ /**
2524
+ * The voice manager
2525
+ * @type {DisTubeVoiceManager}
2526
+ * @readonly
2527
+ */
1981
2528
  get voices() {
1982
2529
  return this.distube.voices;
1983
2530
  }
2531
+ /**
2532
+ * Discord.js client
2533
+ * @type {Discord.Client}
2534
+ * @readonly
2535
+ */
1984
2536
  get client() {
1985
2537
  return this.distube.client;
1986
2538
  }
2539
+ /**
2540
+ * DisTube options
2541
+ * @type {DisTubeOptions}
2542
+ * @readonly
2543
+ */
1987
2544
  get options() {
1988
2545
  return this.distube.options;
1989
2546
  }
2547
+ /**
2548
+ * DisTube handler
2549
+ * @type {DisTubeHandler}
2550
+ * @readonly
2551
+ */
1990
2552
  get handler() {
1991
2553
  return this.distube.handler;
1992
2554
  }
2555
+ /**
2556
+ * Check if the string is working with this plugin
2557
+ * @param {string} _string Input string
2558
+ * @returns {boolean|Promise<boolean>}
2559
+ */
1993
2560
  validate(_string) {
1994
2561
  return false;
1995
2562
  }
2563
+ /**
2564
+ * Get the stream url from {@link Song#url}. Returns {@link Song#url} by default.
2565
+ * Not needed if the plugin plays song from YouTube.
2566
+ * @param {string} url Input url
2567
+ * @returns {string|Promise<string>}
2568
+ */
1996
2569
  getStreamURL(url) {
1997
2570
  return url;
1998
2571
  }
2572
+ /**
2573
+ * Get related songs from a supported url. {@link Song#member} should be `undefined`.
2574
+ * Not needed to add {@link Song#related} because it will be added with this function later.
2575
+ * @param {string} _url Input url
2576
+ * @returns {Song[]|Promise<Song[]>}
2577
+ */
1999
2578
  getRelatedSongs(_url) {
2000
2579
  return [];
2001
2580
  }
2002
2581
  };
2003
- __name(Plugin, "Plugin");
2582
+ __name(_Plugin, "Plugin");
2583
+ var Plugin = _Plugin;
2004
2584
 
2005
2585
  // src/struct/CustomPlugin.ts
2006
- var CustomPlugin = class extends Plugin {
2586
+ var _CustomPlugin = class _CustomPlugin extends Plugin {
2007
2587
  constructor() {
2008
2588
  super(...arguments);
2009
2589
  __publicField(this, "type", "custom" /* CUSTOM */);
2010
2590
  }
2011
2591
  };
2012
- __name(CustomPlugin, "CustomPlugin");
2592
+ __name(_CustomPlugin, "CustomPlugin");
2593
+ var CustomPlugin = _CustomPlugin;
2013
2594
 
2014
2595
  // src/struct/ExtractorPlugin.ts
2015
- var ExtractorPlugin = class extends Plugin {
2596
+ var _ExtractorPlugin = class _ExtractorPlugin extends Plugin {
2016
2597
  constructor() {
2017
2598
  super(...arguments);
2018
2599
  __publicField(this, "type", "extractor" /* EXTRACTOR */);
2019
2600
  }
2020
2601
  };
2021
- __name(ExtractorPlugin, "ExtractorPlugin");
2602
+ __name(_ExtractorPlugin, "ExtractorPlugin");
2603
+ var ExtractorPlugin = _ExtractorPlugin;
2022
2604
 
2023
2605
  // src/util.ts
2024
2606
  var import_url = require("url");
@@ -2027,7 +2609,7 @@ var formatInt = /* @__PURE__ */ __name((int) => int < 10 ? `0${int}` : int, "for
2027
2609
  function formatDuration(sec) {
2028
2610
  if (!sec || !Number(sec))
2029
2611
  return "00:00";
2030
- const seconds = Math.round(sec % 60);
2612
+ const seconds = Math.floor(sec % 60);
2031
2613
  const minutes = Math.floor(sec % 3600 / 60);
2032
2614
  const hours = Math.floor(sec / 3600);
2033
2615
  if (hours > 0)
@@ -2062,12 +2644,13 @@ function parseNumber(input) {
2062
2644
  return Number(input) || 0;
2063
2645
  }
2064
2646
  __name(parseNumber, "parseNumber");
2647
+ var SUPPORTED_PROTOCOL = ["https:", "http:", "file:"];
2065
2648
  function isURL(input) {
2066
2649
  if (typeof input !== "string" || input.includes(" "))
2067
2650
  return false;
2068
2651
  try {
2069
2652
  const url = new import_url.URL(input);
2070
- if (!["https:", "http:"].includes(url.protocol) || !url.host)
2653
+ if (!SUPPORTED_PROTOCOL.some((p) => p === url.protocol))
2071
2654
  return false;
2072
2655
  } catch {
2073
2656
  return false;
@@ -2106,7 +2689,7 @@ function isMemberInstance(member) {
2106
2689
  }
2107
2690
  __name(isMemberInstance, "isMemberInstance");
2108
2691
  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";
2692
+ 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
2693
  }
2111
2694
  __name(isTextChannelInstance, "isTextChannelInstance");
2112
2695
  function isMessageInstance(message) {
@@ -2166,20 +2749,30 @@ function objectKeys(obj) {
2166
2749
  return Object.keys(obj);
2167
2750
  }
2168
2751
  __name(objectKeys, "objectKeys");
2752
+ function isNsfwChannel(channel) {
2753
+ if (!isTextChannelInstance(channel))
2754
+ return false;
2755
+ if (channel.isThread())
2756
+ return channel.parent?.nsfw ?? false;
2757
+ return channel.nsfw;
2758
+ }
2759
+ __name(isNsfwChannel, "isNsfwChannel");
2169
2760
 
2170
2761
  // src/plugin/DirectLink.ts
2171
2762
  var import_undici = require("undici");
2172
- var DirectLinkPlugin = class extends ExtractorPlugin {
2763
+ var _DirectLinkPlugin = class _DirectLinkPlugin extends ExtractorPlugin {
2173
2764
  async validate(url) {
2174
2765
  try {
2175
2766
  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"))
2767
+ const types = headers["content-type"];
2768
+ const type = Array.isArray(types) ? types[0] : types;
2769
+ if (["audio/", "video/", "application/ogg"].some((s) => type?.startsWith(s)))
2178
2770
  return true;
2179
2771
  } catch {
2180
2772
  }
2181
2773
  return false;
2182
2774
  }
2775
+ // eslint-disable-next-line @typescript-eslint/require-await
2183
2776
  async resolve(url, options = {}) {
2184
2777
  url = url.replace(/\/+$/, "");
2185
2778
  return new Song(
@@ -2192,13 +2785,28 @@ var DirectLinkPlugin = class extends ExtractorPlugin {
2192
2785
  );
2193
2786
  }
2194
2787
  };
2195
- __name(DirectLinkPlugin, "DirectLinkPlugin");
2788
+ __name(_DirectLinkPlugin, "DirectLinkPlugin");
2789
+ var DirectLinkPlugin = _DirectLinkPlugin;
2196
2790
 
2197
2791
  // src/DisTube.ts
2198
2792
  var import_ytsr = __toESM(require("@distube/ytsr"));
2199
2793
  var import_tiny_typed_emitter2 = require("tiny-typed-emitter");
2200
2794
  var { version } = require_package();
2201
- var DisTube = class extends import_tiny_typed_emitter2.TypedEmitter {
2795
+ var _DisTube = class _DisTube extends import_tiny_typed_emitter2.TypedEmitter {
2796
+ /**
2797
+ * Create a new DisTube class.
2798
+ * @param {Discord.Client} client Discord.JS client
2799
+ * @param {DisTubeOptions} [otp] Custom DisTube options
2800
+ * @throws {DisTubeError}
2801
+ * @example
2802
+ * const Discord = require('discord.js'),
2803
+ * DisTube = require('distube'),
2804
+ * client = new Discord.Client();
2805
+ * // Create a new DisTube
2806
+ * const distube = new DisTube.default(client, { searchSongs: 10 });
2807
+ * // client.DisTube = distube // make it access easily
2808
+ * client.login("Your Discord Bot Token")
2809
+ */
2202
2810
  constructor(client, otp = {}) {
2203
2811
  super();
2204
2812
  __publicField(this, "handler");
@@ -2228,9 +2836,36 @@ var DisTube = class extends import_tiny_typed_emitter2.TypedEmitter {
2228
2836
  static get version() {
2229
2837
  return version;
2230
2838
  }
2839
+ /**
2840
+ * DisTube version
2841
+ * @type {string}
2842
+ */
2231
2843
  get version() {
2232
2844
  return version;
2233
2845
  }
2846
+ /**
2847
+ * Play / add a song or playlist from url. Search and play a song if it is not a valid url.
2848
+ *
2849
+ * @param {Discord.BaseGuildVoiceChannel} voiceChannel The channel will be joined if the bot isn't in any channels,
2850
+ * the bot will be moved to this channel if {@link DisTubeOptions}.joinNewVoiceChannel is `true`
2851
+ * @param {string|Song|SearchResult|Playlist} song URL | Search string |
2852
+ * {@link Song} | {@link SearchResult} | {@link Playlist}
2853
+ * @param {PlayOptions} [options] Optional options
2854
+ * @throws {DisTubeError}
2855
+ * @example
2856
+ * client.on('message', (message) => {
2857
+ * if (!message.content.startsWith(config.prefix)) return;
2858
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
2859
+ * const command = args.shift();
2860
+ * if (command == "play")
2861
+ * distube.play(message.member.voice.channel, args.join(" "), {
2862
+ * member: message.member,
2863
+ * textChannel: message.channel,
2864
+ * message
2865
+ * });
2866
+ * });
2867
+ * @returns {Promise<void>}
2868
+ */
2234
2869
  async play(voiceChannel, song, options = {}) {
2235
2870
  if (!isSupportedVoiceChannel(voiceChannel)) {
2236
2871
  throw new DisTubeError("INVALID_TYPE", "BaseGuildVoiceChannel", voiceChannel, "voiceChannel");
@@ -2297,6 +2932,20 @@ ${e.message}`;
2297
2932
  queue?._taskQueue.resolve();
2298
2933
  }
2299
2934
  }
2935
+ /**
2936
+ * Create a custom playlist
2937
+ * @returns {Promise<Playlist>}
2938
+ * @param {Array<string|Song|SearchResult>} songs Array of url, Song or SearchResult
2939
+ * @param {CustomPlaylistOptions} [options] Optional options
2940
+ * @example
2941
+ * const songs = ["https://www.youtube.com/watch?v=xxx", "https://www.youtube.com/watch?v=yyy"];
2942
+ * const playlist = await distube.createCustomPlaylist(songs, {
2943
+ * member: message.member,
2944
+ * properties: { name: "My playlist name", source: "custom" },
2945
+ * parallel: true
2946
+ * });
2947
+ * distube.play(voiceChannel, playlist, { ... });
2948
+ */
2300
2949
  async createCustomPlaylist(songs, options = {}) {
2301
2950
  const { member, properties, parallel, metadata } = { parallel: true, ...options };
2302
2951
  if (!Array.isArray(songs))
@@ -2311,8 +2960,6 @@ ${e.message}`;
2311
2960
  if (member && !isMemberInstance(member)) {
2312
2961
  throw new DisTubeError("INVALID_TYPE", "Discord.Member", member, "options.member");
2313
2962
  }
2314
- if (!filteredSongs.length)
2315
- throw new DisTubeError("NO_VALID_SONG");
2316
2963
  let resolvedSongs;
2317
2964
  if (parallel) {
2318
2965
  const promises = filteredSongs.map(
@@ -2328,6 +2975,18 @@ ${e.message}`;
2328
2975
  }
2329
2976
  return new Playlist(resolvedSongs, { member, properties, metadata });
2330
2977
  }
2978
+ /**
2979
+ * Search for a song. You can customize how user answers instead of send a number.
2980
+ * Then use {@link DisTube#play} to play it.
2981
+ *
2982
+ * @param {string} string The string search for
2983
+ * @param {Object} options Search options
2984
+ * @param {number} [options.limit=10] Limit the results
2985
+ * @param {SearchResultType} [options.type=SearchResultType.VIDEO] Type of results (`video` or `playlist`).
2986
+ * @param {boolean} [options.safeSearch=false] Whether or not use safe search (YouTube restricted mode)
2987
+ * @throws {Error}
2988
+ * @returns {Promise<Array<SearchResult>>} Array of results
2989
+ */
2331
2990
  async search(string, options = {}) {
2332
2991
  const opts = { type: "video" /* VIDEO */, limit: 10, safeSearch: false, ...options };
2333
2992
  if (typeof opts.type !== "string" || !["video", "playlist"].includes(opts.type)) {
@@ -2357,63 +3016,234 @@ ${e.message}`;
2357
3016
  return this.search(string, options);
2358
3017
  }
2359
3018
  }
3019
+ /**
3020
+ * Get the guild queue
3021
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3022
+ * @returns {Queue?}
3023
+ * @throws {Error}
3024
+ * @example
3025
+ * client.on('message', (message) => {
3026
+ * if (!message.content.startsWith(config.prefix)) return;
3027
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3028
+ * const command = args.shift();
3029
+ * if (command == "queue") {
3030
+ * const queue = distube.getQueue(message);
3031
+ * message.channel.send('Current queue:\n' + queue.songs.map((song, id) =>
3032
+ * `**${id+1}**. [${song.name}](${song.url}) - \`${song.formattedDuration}\``
3033
+ * ).join("\n"));
3034
+ * }
3035
+ * });
3036
+ */
2360
3037
  getQueue(guild) {
2361
3038
  return this.queues.get(guild);
2362
3039
  }
3040
+ /**
3041
+ * Pause the guild stream
3042
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3043
+ * @returns {Queue} The guild queue
3044
+ * @throws {Error}
3045
+ */
2363
3046
  pause(guild) {
2364
3047
  const q = this.getQueue(guild);
2365
3048
  if (!q)
2366
3049
  throw new DisTubeError("NO_QUEUE");
2367
3050
  return q.pause();
2368
3051
  }
3052
+ /**
3053
+ * Resume the guild stream
3054
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3055
+ * @returns {Queue} The guild queue
3056
+ * @throws {Error}
3057
+ */
2369
3058
  resume(guild) {
2370
3059
  const q = this.getQueue(guild);
2371
3060
  if (!q)
2372
3061
  throw new DisTubeError("NO_QUEUE");
2373
3062
  return q.resume();
2374
3063
  }
3064
+ /**
3065
+ * Stop the guild stream
3066
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3067
+ * @returns {Promise<void>}
3068
+ * @throws {Error}
3069
+ * @example
3070
+ * client.on('message', (message) => {
3071
+ * if (!message.content.startsWith(config.prefix)) return;
3072
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3073
+ * const command = args.shift();
3074
+ * if (command == "stop") {
3075
+ * distube.stop(message);
3076
+ * message.channel.send("Stopped the queue!");
3077
+ * }
3078
+ * });
3079
+ */
2375
3080
  stop(guild) {
2376
3081
  const q = this.getQueue(guild);
2377
3082
  if (!q)
2378
3083
  throw new DisTubeError("NO_QUEUE");
2379
3084
  return q.stop();
2380
3085
  }
3086
+ /**
3087
+ * Set the guild stream's volume
3088
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3089
+ * @param {number} percent The percentage of volume you want to set
3090
+ * @returns {Queue} The guild queue
3091
+ * @throws {Error}
3092
+ * @example
3093
+ * client.on('message', (message) => {
3094
+ * if (!message.content.startsWith(config.prefix)) return;
3095
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3096
+ * const command = args.shift();
3097
+ * if (command == "volume")
3098
+ * distube.setVolume(message, Number(args[0]));
3099
+ * });
3100
+ */
2381
3101
  setVolume(guild, percent) {
2382
3102
  const q = this.getQueue(guild);
2383
3103
  if (!q)
2384
3104
  throw new DisTubeError("NO_QUEUE");
2385
3105
  return q.setVolume(percent);
2386
3106
  }
3107
+ /**
3108
+ * Skip the playing song if there is a next song in the queue.
3109
+ * <info>If {@link Queue#autoplay} is `true` and there is no up next song,
3110
+ * DisTube will add and play a related song.</info>
3111
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3112
+ * @returns {Promise<Song>} The new Song will be played
3113
+ * @throws {Error}
3114
+ * @example
3115
+ * client.on('message', (message) => {
3116
+ * if (!message.content.startsWith(config.prefix)) return;
3117
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3118
+ * const command = args.shift();
3119
+ * if (command == "skip")
3120
+ * distube.skip(message);
3121
+ * });
3122
+ */
2387
3123
  skip(guild) {
2388
3124
  const q = this.getQueue(guild);
2389
3125
  if (!q)
2390
3126
  throw new DisTubeError("NO_QUEUE");
2391
3127
  return q.skip();
2392
3128
  }
3129
+ /**
3130
+ * Play the previous song
3131
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3132
+ * @returns {Promise<Song>} The new Song will be played
3133
+ * @throws {Error}
3134
+ * @example
3135
+ * client.on('message', (message) => {
3136
+ * if (!message.content.startsWith(config.prefix)) return;
3137
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3138
+ * const command = args.shift();
3139
+ * if (command == "previous")
3140
+ * distube.previous(message);
3141
+ * });
3142
+ */
2393
3143
  previous(guild) {
2394
3144
  const q = this.getQueue(guild);
2395
3145
  if (!q)
2396
3146
  throw new DisTubeError("NO_QUEUE");
2397
3147
  return q.previous();
2398
3148
  }
3149
+ /**
3150
+ * Shuffle the guild queue songs
3151
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3152
+ * @returns {Promise<Queue>} The guild queue
3153
+ * @example
3154
+ * client.on('message', (message) => {
3155
+ * if (!message.content.startsWith(config.prefix)) return;
3156
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3157
+ * const command = args.shift();
3158
+ * if (command == "shuffle")
3159
+ * distube.shuffle(message);
3160
+ * });
3161
+ */
2399
3162
  shuffle(guild) {
2400
3163
  const q = this.getQueue(guild);
2401
3164
  if (!q)
2402
3165
  throw new DisTubeError("NO_QUEUE");
2403
3166
  return q.shuffle();
2404
3167
  }
3168
+ /**
3169
+ * Jump to the song number in the queue.
3170
+ * The next one is 1, 2,...
3171
+ * The previous one is -1, -2,...
3172
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3173
+ * @param {number} num The song number to play
3174
+ * @returns {Promise<Song>} The new Song will be played
3175
+ * @throws {Error} if `num` is invalid number (0 < num < {@link Queue#songs}.length)
3176
+ * @example
3177
+ * client.on('message', (message) => {
3178
+ * if (!message.content.startsWith(config.prefix)) return;
3179
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3180
+ * const command = args.shift();
3181
+ * if (command == "jump")
3182
+ * distube.jump(message, parseInt(args[0]))
3183
+ * .catch(err => message.channel.send("Invalid song number."));
3184
+ * });
3185
+ */
2405
3186
  jump(guild, num) {
2406
3187
  const q = this.getQueue(guild);
2407
3188
  if (!q)
2408
3189
  throw new DisTubeError("NO_QUEUE");
2409
3190
  return q.jump(num);
2410
3191
  }
3192
+ /**
3193
+ * Set the repeat mode of the guild queue.\
3194
+ * Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
3195
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3196
+ * @param {RepeatMode?} [mode] The repeat modes (toggle if `undefined`)
3197
+ * @returns {RepeatMode} The new repeat mode
3198
+ * @example
3199
+ * client.on('message', (message) => {
3200
+ * if (!message.content.startsWith(config.prefix)) return;
3201
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3202
+ * const command = args.shift();
3203
+ * if (command == "repeat") {
3204
+ * let mode = distube.setRepeatMode(message, parseInt(args[0]));
3205
+ * mode = mode ? mode == 2 ? "Repeat queue" : "Repeat song" : "Off";
3206
+ * message.channel.send("Set repeat mode to `" + mode + "`");
3207
+ * }
3208
+ * });
3209
+ * @example
3210
+ * const { RepeatMode } = require("distube");
3211
+ * let mode;
3212
+ * switch(distube.setRepeatMode(message, parseInt(args[0]))) {
3213
+ * case RepeatMode.DISABLED:
3214
+ * mode = "Off";
3215
+ * break;
3216
+ * case RepeatMode.SONG:
3217
+ * mode = "Repeat a song";
3218
+ * break;
3219
+ * case RepeatMode.QUEUE:
3220
+ * mode = "Repeat all queue";
3221
+ * break;
3222
+ * }
3223
+ * message.channel.send("Set repeat mode to `" + mode + "`");
3224
+ */
2411
3225
  setRepeatMode(guild, mode) {
2412
3226
  const q = this.getQueue(guild);
2413
3227
  if (!q)
2414
3228
  throw new DisTubeError("NO_QUEUE");
2415
3229
  return q.setRepeatMode(mode);
2416
3230
  }
3231
+ /**
3232
+ * Toggle autoplay mode
3233
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3234
+ * @returns {boolean} Autoplay mode state
3235
+ * @throws {Error}
3236
+ * @example
3237
+ * client.on('message', (message) => {
3238
+ * if (!message.content.startsWith(config.prefix)) return;
3239
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3240
+ * const command = args.shift();
3241
+ * if (command == "autoplay") {
3242
+ * const mode = distube.toggleAutoplay(message);
3243
+ * message.channel.send("Set autoplay mode to `" + (mode ? "On" : "Off") + "`");
3244
+ * }
3245
+ * });
3246
+ */
2417
3247
  toggleAutoplay(guild) {
2418
3248
  const q = this.getQueue(guild);
2419
3249
  if (!q)
@@ -2421,18 +3251,43 @@ ${e.message}`;
2421
3251
  q.autoplay = !q.autoplay;
2422
3252
  return q.autoplay;
2423
3253
  }
3254
+ /**
3255
+ * Add related song to the queue
3256
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3257
+ * @returns {Promise<Song>} The guild queue
3258
+ */
2424
3259
  addRelatedSong(guild) {
2425
3260
  const q = this.getQueue(guild);
2426
3261
  if (!q)
2427
3262
  throw new DisTubeError("NO_QUEUE");
2428
3263
  return q.addRelatedSong();
2429
3264
  }
3265
+ /**
3266
+ * Set the playing time to another position
3267
+ * @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
3268
+ * @param {number} time Time in seconds
3269
+ * @returns {Queue} Seeked queue
3270
+ * @example
3271
+ * client.on('message', message => {
3272
+ * if (!message.content.startsWith(config.prefix)) return;
3273
+ * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3274
+ * const command = args.shift();
3275
+ * if (command = 'seek')
3276
+ * distube.seek(message, Number(args[0]));
3277
+ * });
3278
+ */
2430
3279
  seek(guild, time) {
2431
3280
  const q = this.getQueue(guild);
2432
3281
  if (!q)
2433
3282
  throw new DisTubeError("NO_QUEUE");
2434
3283
  return q.seek(time);
2435
3284
  }
3285
+ /**
3286
+ * Emit error event
3287
+ * @param {Error} error error
3288
+ * @param {Discord.BaseGuildTextChannel} [channel] Text channel where the error is encountered.
3289
+ * @private
3290
+ */
2436
3291
  emitError(error, channel) {
2437
3292
  if (this.listeners("error").length) {
2438
3293
  this.emit("error", channel, error);
@@ -2445,7 +3300,8 @@ ${e.message}`;
2445
3300
  }
2446
3301
  }
2447
3302
  };
2448
- __name(DisTube, "DisTube");
3303
+ __name(_DisTube, "DisTube");
3304
+ var DisTube = _DisTube;
2449
3305
  // Annotate the CommonJS export names for ESM import in node:
2450
3306
  0 && (module.exports = {
2451
3307
  BaseManager,
@@ -2485,6 +3341,7 @@ __name(DisTube, "DisTube");
2485
3341
  isGuildInstance,
2486
3342
  isMemberInstance,
2487
3343
  isMessageInstance,
3344
+ isNsfwChannel,
2488
3345
  isObject,
2489
3346
  isRecord,
2490
3347
  isSnowflake,