distube 4.0.3 → 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
@@ -22,7 +22,14 @@ var __copyProps = (to, from, except, desc) => {
22
22
  }
23
23
  return to;
24
24
  };
25
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod));
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.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
26
33
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
27
34
  var __publicField = (obj, key, value) => {
28
35
  __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -56,7 +63,7 @@ var require_package = __commonJS({
56
63
  "package.json"(exports, module2) {
57
64
  module2.exports = {
58
65
  name: "distube",
59
- version: "4.0.3",
66
+ version: "4.0.5",
60
67
  description: "A Discord.js module to simplify your music commands and play songs with audio filters on Discord without any API key.",
61
68
  main: "./dist/index.js",
62
69
  types: "./dist/index.d.ts",
@@ -70,7 +77,7 @@ var require_package = __commonJS({
70
77
  ],
71
78
  scripts: {
72
79
  test: "jest",
73
- docs: "docgen -s src/*.ts src/**/*.ts -o docs.json -c pages/index.yml -g -j jsdoc.config.json",
80
+ docs: "docgen -s src/**/*.ts -o docs.json -c pages/index.yml -g -j jsdoc.config.json",
74
81
  lint: "prettier --check . && eslint .",
75
82
  "lint:fix": "eslint . --fix",
76
83
  prettier: 'prettier --write "**/*.{ts,json,yml,yaml,md}"',
@@ -80,7 +87,8 @@ var require_package = __commonJS({
80
87
  postinstall: "husky install",
81
88
  prepublishOnly: "yarn lint && yarn test",
82
89
  prepack: "yarn build && pinst --disable",
83
- postpack: "pinst --enable"
90
+ postpack: "pinst --enable",
91
+ "dev:add-docs-to-worktree": "git worktree add --track -b docs docs origin/docs"
84
92
  },
85
93
  repository: {
86
94
  type: "git",
@@ -116,44 +124,45 @@ var require_package = __commonJS({
116
124
  ],
117
125
  homepage: "https://distube.js.org/",
118
126
  dependencies: {
119
- "@distube/ytdl-core": "^4.11.3",
127
+ "@distube/ytdl-core": "^4.11.12",
120
128
  "@distube/ytpl": "^1.1.1",
121
- "@distube/ytsr": "^1.1.8",
129
+ "@distube/ytsr": "^1.1.9",
122
130
  "prism-media": "https://codeload.github.com/distubejs/prism-media/tar.gz/main#workaround.tar.gz",
123
131
  "tiny-typed-emitter": "^2.1.0",
124
- tslib: "^2.4.0",
125
- undici: "^5.8.0"
132
+ tslib: "^2.6.0",
133
+ undici: "^5.22.1"
126
134
  },
127
135
  devDependencies: {
128
- "@babel/core": "^7.18.9",
136
+ "@babel/core": "^7.22.9",
129
137
  "@babel/plugin-proposal-class-properties": "^7.18.6",
130
- "@babel/plugin-proposal-object-rest-spread": "^7.18.9",
131
- "@babel/preset-env": "^7.18.9",
132
- "@babel/preset-typescript": "^7.18.6",
133
- "@commitlint/cli": "^17.0.3",
134
- "@commitlint/config-conventional": "^17.0.3",
135
- "@discordjs/voice": "^0.11.0",
136
- "@distube/docgen": "distubejs/docgen",
137
- "@types/jest": "^28.1.6",
138
- "@types/node": "^18.6.0",
139
- "@typescript-eslint/eslint-plugin": "^5.30.7",
140
- "@typescript-eslint/parser": "^5.30.7",
141
- "babel-jest": "^28.1.3",
142
- "discord.js": "^14.0.3",
143
- eslint: "^8.20.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",
145
+ "@distubejs/docgen": "distubejs/docgen",
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",
144
153
  "eslint-config-distube": "^1.6.4",
145
- "eslint-config-prettier": "^8.5.0",
146
- "eslint-plugin-deprecation": "^1.3.2",
147
- "eslint-plugin-jsdoc": "^39.3.3",
148
- husky: "^8.0.1",
149
- 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",
150
159
  "jsdoc-babel": "^0.5.0",
151
160
  "nano-staged": "^0.8.0",
152
- "npm-check-updates": "^16.0.0",
161
+ "npm-check-updates": "^16.10.16",
153
162
  pinst: "^3.0.0",
154
- prettier: "^2.7.1",
155
- tsup: "^6.1.3",
156
- typescript: "^4.7.4"
163
+ prettier: "^3.0.0",
164
+ tsup: "^7.1.0",
165
+ typescript: "^5.1.6"
157
166
  },
158
167
  peerDependencies: {
159
168
  "@discordjs/opus": "*",
@@ -177,7 +186,7 @@ var require_package = __commonJS({
177
186
  engines: {
178
187
  node: ">=16.9.0"
179
188
  },
180
- packageManager: "yarn@3.2.0"
189
+ packageManager: "yarn@3.6.1"
181
190
  };
182
191
  }
183
192
  });
@@ -223,6 +232,7 @@ __export(src_exports, {
223
232
  isGuildInstance: () => isGuildInstance,
224
233
  isMemberInstance: () => isMemberInstance,
225
234
  isMessageInstance: () => isMessageInstance,
235
+ isNsfwChannel: () => isNsfwChannel,
226
236
  isObject: () => isObject,
227
237
  isRecord: () => isRecord,
228
238
  isSnowflake: () => isSnowflake,
@@ -239,25 +249,6 @@ __export(src_exports, {
239
249
  module.exports = __toCommonJS(src_exports);
240
250
 
241
251
  // src/type.ts
242
- var Events = /* @__PURE__ */ ((Events2) => {
243
- Events2["ERROR"] = "error";
244
- Events2["ADD_LIST"] = "addList";
245
- Events2["ADD_SONG"] = "addSong";
246
- Events2["PLAY_SONG"] = "playSong";
247
- Events2["FINISH_SONG"] = "finishSong";
248
- Events2["EMPTY"] = "empty";
249
- Events2["FINISH"] = "finish";
250
- Events2["INIT_QUEUE"] = "initQueue";
251
- Events2["NO_RELATED"] = "noRelated";
252
- Events2["DISCONNECT"] = "disconnect";
253
- Events2["DELETE_QUEUE"] = "deleteQueue";
254
- Events2["SEARCH_CANCEL"] = "searchCancel";
255
- Events2["SEARCH_NO_RESULT"] = "searchNoResult";
256
- Events2["SEARCH_DONE"] = "searchDone";
257
- Events2["SEARCH_INVALID_ANSWER"] = "searchInvalidAnswer";
258
- Events2["SEARCH_RESULT"] = "searchResult";
259
- return Events2;
260
- })(Events || {});
261
252
  var RepeatMode = /* @__PURE__ */ ((RepeatMode2) => {
262
253
  RepeatMode2[RepeatMode2["DISABLED"] = 0] = "DISABLED";
263
254
  RepeatMode2[RepeatMode2["SONG"] = 1] = "SONG";
@@ -279,6 +270,25 @@ var StreamType = /* @__PURE__ */ ((StreamType2) => {
279
270
  StreamType2[StreamType2["RAW"] = 1] = "RAW";
280
271
  return StreamType2;
281
272
  })(StreamType || {});
273
+ var Events = /* @__PURE__ */ ((Events2) => {
274
+ Events2["ERROR"] = "error";
275
+ Events2["ADD_LIST"] = "addList";
276
+ Events2["ADD_SONG"] = "addSong";
277
+ Events2["PLAY_SONG"] = "playSong";
278
+ Events2["FINISH_SONG"] = "finishSong";
279
+ Events2["EMPTY"] = "empty";
280
+ Events2["FINISH"] = "finish";
281
+ Events2["INIT_QUEUE"] = "initQueue";
282
+ Events2["NO_RELATED"] = "noRelated";
283
+ Events2["DISCONNECT"] = "disconnect";
284
+ Events2["DELETE_QUEUE"] = "deleteQueue";
285
+ Events2["SEARCH_CANCEL"] = "searchCancel";
286
+ Events2["SEARCH_NO_RESULT"] = "searchNoResult";
287
+ Events2["SEARCH_DONE"] = "searchDone";
288
+ Events2["SEARCH_INVALID_ANSWER"] = "searchInvalidAnswer";
289
+ Events2["SEARCH_RESULT"] = "searchResult";
290
+ return Events2;
291
+ })(Events || {});
282
292
 
283
293
  // src/constant.ts
284
294
  var defaultFilters = {
@@ -361,13 +371,13 @@ var ERROR_MESSAGES = {
361
371
  var haveCode = /* @__PURE__ */ __name((code) => Object.keys(ERROR_MESSAGES).includes(code), "haveCode");
362
372
  var parseMessage = /* @__PURE__ */ __name((m, ...args) => typeof m === "string" ? m : m(...args), "parseMessage");
363
373
  var getErrorMessage = /* @__PURE__ */ __name((code, ...args) => haveCode(code) ? parseMessage(ERROR_MESSAGES[code], ...args) : args[0], "getErrorMessage");
364
- var DisTubeError = class extends Error {
374
+ var _DisTubeError = class _DisTubeError extends Error {
365
375
  constructor(code, ...args) {
366
376
  super(getErrorMessage(code, ...args));
367
377
  __publicField(this, "errorCode");
368
378
  this.errorCode = code;
369
379
  if (Error.captureStackTrace)
370
- Error.captureStackTrace(this, DisTubeError);
380
+ Error.captureStackTrace(this, _DisTubeError);
371
381
  }
372
382
  get name() {
373
383
  return `DisTubeError [${this.errorCode}]`;
@@ -376,10 +386,11 @@ var DisTubeError = class extends Error {
376
386
  return this.errorCode;
377
387
  }
378
388
  };
379
- __name(DisTubeError, "DisTubeError");
389
+ __name(_DisTubeError, "DisTubeError");
390
+ var DisTubeError = _DisTubeError;
380
391
 
381
392
  // src/struct/TaskQueue.ts
382
- var Task = class {
393
+ var _Task = class _Task {
383
394
  constructor(resolveInfo) {
384
395
  __publicField(this, "resolve");
385
396
  __publicField(this, "promise");
@@ -390,33 +401,64 @@ var Task = class {
390
401
  });
391
402
  }
392
403
  };
393
- __name(Task, "Task");
404
+ __name(_Task, "Task");
405
+ var Task = _Task;
394
406
  var _tasks;
395
- var TaskQueue = class {
407
+ var _TaskQueue = class _TaskQueue {
396
408
  constructor() {
409
+ /**
410
+ * The task array
411
+ * @type {Task[]}
412
+ * @private
413
+ */
397
414
  __privateAdd(this, _tasks, []);
398
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
+ */
399
421
  queuing(resolveInfo = false) {
400
422
  const next = this.remaining ? __privateGet(this, _tasks)[__privateGet(this, _tasks).length - 1].promise : Promise.resolve();
401
423
  __privateGet(this, _tasks).push(new Task(resolveInfo));
402
424
  return next;
403
425
  }
426
+ /**
427
+ * Removes the finished task and processes the next task
428
+ */
404
429
  resolve() {
405
430
  __privateGet(this, _tasks).shift()?.resolve();
406
431
  }
432
+ /**
433
+ * The remaining number of tasks
434
+ * @type {number}
435
+ */
407
436
  get remaining() {
408
437
  return __privateGet(this, _tasks).length;
409
438
  }
439
+ /**
440
+ * Whether or not having a resolving info task
441
+ * @type {boolean}
442
+ */
410
443
  get hasResolveTask() {
411
444
  return !!__privateGet(this, _tasks).find((t) => t.resolveInfo);
412
445
  }
413
446
  };
414
- __name(TaskQueue, "TaskQueue");
415
447
  _tasks = new WeakMap();
448
+ __name(_TaskQueue, "TaskQueue");
449
+ var TaskQueue = _TaskQueue;
416
450
 
417
451
  // src/struct/Playlist.ts
418
452
  var _metadata, _member;
419
- 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
+ */
420
462
  constructor(playlist, options = {}) {
421
463
  __publicField(this, "source");
422
464
  __publicField(this, "songs");
@@ -445,7 +487,8 @@ var Playlist = class {
445
487
  if (!Array.isArray(playlist.songs) || !playlist.songs.length)
446
488
  throw new DisTubeError("EMPTY_PLAYLIST");
447
489
  this.songs = playlist.songs;
448
- 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`);
449
492
  this.url = playlist.url || playlist.webpage_url;
450
493
  this.thumbnail = playlist.thumbnail || this.songs[0].thumbnail;
451
494
  this.member = member || playlist.member || void 0;
@@ -456,12 +499,24 @@ var Playlist = class {
456
499
  this[key] = value;
457
500
  this.metadata = metadata;
458
501
  }
502
+ /**
503
+ * Playlist duration in second.
504
+ * @type {number}
505
+ */
459
506
  get duration() {
460
507
  return this.songs?.reduce((prev, next) => prev + (next.duration || 0), 0) || 0;
461
508
  }
509
+ /**
510
+ * Formatted duration string `hh:mm:ss`.
511
+ * @type {string}
512
+ */
462
513
  get formattedDuration() {
463
514
  return formatDuration(this.duration);
464
515
  }
516
+ /**
517
+ * User requested.
518
+ * @type {Discord.GuildMember?}
519
+ */
465
520
  get member() {
466
521
  return __privateGet(this, _member);
467
522
  }
@@ -471,6 +526,10 @@ var Playlist = class {
471
526
  __privateSet(this, _member, member);
472
527
  this.songs.map((s) => s.constructor.name === "Song" && (s.member = this.member));
473
528
  }
529
+ /**
530
+ * User requested.
531
+ * @type {Discord.User?}
532
+ */
474
533
  get user() {
475
534
  return this.member?.user;
476
535
  }
@@ -482,12 +541,17 @@ var Playlist = class {
482
541
  this.songs.map((s) => s.constructor.name === "Song" && (s.metadata = metadata));
483
542
  }
484
543
  };
485
- __name(Playlist, "Playlist");
486
544
  _metadata = new WeakMap();
487
545
  _member = new WeakMap();
546
+ __name(_Playlist, "Playlist");
547
+ var Playlist = _Playlist;
488
548
 
489
549
  // src/struct/SearchResult.ts
490
- var ISearchResult = class {
550
+ var _ISearchResult = class _ISearchResult {
551
+ /**
552
+ * Create a search result
553
+ * @param {Object} info ytsr result
554
+ */
491
555
  constructor(info) {
492
556
  __publicField(this, "source");
493
557
  __publicField(this, "id");
@@ -504,8 +568,9 @@ var ISearchResult = class {
504
568
  };
505
569
  }
506
570
  };
507
- __name(ISearchResult, "ISearchResult");
508
- var SearchResultVideo = class extends ISearchResult {
571
+ __name(_ISearchResult, "ISearchResult");
572
+ var ISearchResult = _ISearchResult;
573
+ var _SearchResultVideo = class _SearchResultVideo extends ISearchResult {
509
574
  constructor(info) {
510
575
  super(info);
511
576
  __publicField(this, "type");
@@ -528,8 +593,9 @@ var SearchResultVideo = class extends ISearchResult {
528
593
  };
529
594
  }
530
595
  };
531
- __name(SearchResultVideo, "SearchResultVideo");
532
- var SearchResultPlaylist = class extends ISearchResult {
596
+ __name(_SearchResultVideo, "SearchResultVideo");
597
+ var SearchResultVideo = _SearchResultVideo;
598
+ var _SearchResultPlaylist = class _SearchResultPlaylist extends ISearchResult {
533
599
  constructor(info) {
534
600
  super(info);
535
601
  __publicField(this, "type");
@@ -544,11 +610,20 @@ var SearchResultPlaylist = class extends ISearchResult {
544
610
  };
545
611
  }
546
612
  };
547
- __name(SearchResultPlaylist, "SearchResultPlaylist");
613
+ __name(_SearchResultPlaylist, "SearchResultPlaylist");
614
+ var SearchResultPlaylist = _SearchResultPlaylist;
548
615
 
549
616
  // src/struct/Song.ts
550
617
  var _metadata2, _member2, _playlist;
551
- 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
+ */
552
627
  constructor(info, options = {}) {
553
628
  __publicField(this, "source");
554
629
  __privateAdd(this, _metadata2, void 0);
@@ -622,6 +697,11 @@ var _Song = class {
622
697
  this.chapters = details.chapters || [];
623
698
  this.reposts = 0;
624
699
  }
700
+ /**
701
+ * Patch data from other source
702
+ * @param {OtherSongInfo} info Video info
703
+ * @private
704
+ */
625
705
  _patchOther(info) {
626
706
  this.id = info.id;
627
707
  this.name = info.title || info.name;
@@ -652,6 +732,10 @@ var _Song = class {
652
732
  this.age_restricted = info.age_restricted || !!info.age_limit && parseNumber(info.age_limit) >= 18;
653
733
  this.chapters = info.chapters || [];
654
734
  }
735
+ /**
736
+ * The playlist added this song
737
+ * @type {Playlist?}
738
+ */
655
739
  get playlist() {
656
740
  return __privateGet(this, _playlist);
657
741
  }
@@ -661,6 +745,10 @@ var _Song = class {
661
745
  __privateSet(this, _playlist, playlist);
662
746
  this.member = playlist.member;
663
747
  }
748
+ /**
749
+ * User requested.
750
+ * @type {Discord.GuildMember?}
751
+ */
664
752
  get member() {
665
753
  return __privateGet(this, _member2);
666
754
  }
@@ -668,6 +756,10 @@ var _Song = class {
668
756
  if (isMemberInstance(member))
669
757
  __privateSet(this, _member2, member);
670
758
  }
759
+ /**
760
+ * User requested.
761
+ * @type {Discord.User?}
762
+ */
671
763
  get user() {
672
764
  return this.member?.user;
673
765
  }
@@ -678,48 +770,85 @@ var _Song = class {
678
770
  __privateSet(this, _metadata2, metadata);
679
771
  }
680
772
  };
681
- var Song = _Song;
682
- __name(Song, "Song");
683
773
  _metadata2 = new WeakMap();
684
774
  _member2 = new WeakMap();
685
775
  _playlist = new WeakMap();
776
+ __name(_Song, "Song");
777
+ var Song = _Song;
686
778
 
687
779
  // src/core/DisTubeBase.ts
688
- var DisTubeBase = class {
780
+ var _DisTubeBase = class _DisTubeBase {
689
781
  constructor(distube) {
690
782
  __publicField(this, "distube");
691
783
  this.distube = distube;
692
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
+ */
693
791
  emit(eventName, ...args) {
694
792
  return this.distube.emit(eventName, ...args);
695
793
  }
794
+ /**
795
+ * Emit error event
796
+ * @param {Error} error error
797
+ * @param {Discord.BaseGuildTextChannel} [channel] Text channel where the error is encountered.
798
+ */
696
799
  emitError(error, channel) {
697
800
  this.distube.emitError(error, channel);
698
801
  }
802
+ /**
803
+ * The queue manager
804
+ * @type {QueueManager}
805
+ * @readonly
806
+ */
699
807
  get queues() {
700
808
  return this.distube.queues;
701
809
  }
810
+ /**
811
+ * The voice manager
812
+ * @type {DisTubeVoiceManager}
813
+ * @readonly
814
+ */
702
815
  get voices() {
703
816
  return this.distube.voices;
704
817
  }
818
+ /**
819
+ * Discord.js client
820
+ * @type {Discord.Client}
821
+ * @readonly
822
+ */
705
823
  get client() {
706
824
  return this.distube.client;
707
825
  }
826
+ /**
827
+ * DisTube options
828
+ * @type {DisTubeOptions}
829
+ * @readonly
830
+ */
708
831
  get options() {
709
832
  return this.distube.options;
710
833
  }
834
+ /**
835
+ * DisTube handler
836
+ * @type {DisTubeHandler}
837
+ * @readonly
838
+ */
711
839
  get handler() {
712
840
  return this.distube.handler;
713
841
  }
714
842
  };
715
- __name(DisTubeBase, "DisTubeBase");
843
+ __name(_DisTubeBase, "DisTubeBase");
844
+ var DisTubeBase = _DisTubeBase;
716
845
 
717
846
  // src/core/DisTubeVoice.ts
718
847
  var import_discord = require("discord.js");
719
848
  var import_tiny_typed_emitter = require("tiny-typed-emitter");
720
849
  var import_voice = require("@discordjs/voice");
721
850
  var _channel, _volume, _br, br_fn, _join, join_fn;
722
- var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
851
+ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedEmitter {
723
852
  constructor(voiceManager, channel) {
724
853
  super();
725
854
  __privateAdd(this, _br);
@@ -749,26 +878,37 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
749
878
  this.emit("error", error);
750
879
  });
751
880
  this.connection.on(import_voice.VoiceConnectionStatus.Disconnected, (_, newState) => {
752
- if (newState.reason === import_voice.VoiceConnectionDisconnectReason.Manual) {
753
- this.leave();
754
- } 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) {
755
884
  (0, import_voice.entersState)(this.connection, import_voice.VoiceConnectionStatus.Connecting, 5e3).catch(() => {
756
885
  if (![import_voice.VoiceConnectionStatus.Ready, import_voice.VoiceConnectionStatus.Connecting].includes(this.connection.state.status)) {
757
886
  this.leave();
758
887
  }
759
888
  });
760
- } else if (this.connection.rejoinAttempts < 5) {
761
- setTimeout(() => {
762
- this.connection.rejoin();
763
- }, (this.connection.rejoinAttempts + 1) * 5e3).unref();
764
- } else if (this.connection.state.status !== import_voice.VoiceConnectionStatus.Destroyed) {
765
- 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"));
766
902
  }
767
903
  }).on(import_voice.VoiceConnectionStatus.Destroyed, () => {
768
904
  this.leave();
769
905
  }).on("error", () => void 0);
770
906
  this.connection.subscribe(this.audioPlayer);
771
907
  }
908
+ /**
909
+ * The voice channel id the bot is in
910
+ * @type {Snowflake?}
911
+ */
772
912
  get channelId() {
773
913
  return this.connection?.joinConfig?.channelId ?? void 0;
774
914
  }
@@ -808,6 +948,11 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
808
948
  __privateSet(this, _channel, channel);
809
949
  __privateMethod(this, _br, br_fn).call(this);
810
950
  }
951
+ /**
952
+ * Join a voice channel with this connection
953
+ * @param {Discord.BaseGuildVoiceChannel} [channel] A voice channel
954
+ * @returns {Promise<DisTubeVoice>}
955
+ */
811
956
  async join(channel) {
812
957
  const TIMEOUT = 3e4;
813
958
  if (channel)
@@ -824,6 +969,10 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
824
969
  }
825
970
  return this;
826
971
  }
972
+ /**
973
+ * Leave the voice channel of this connection
974
+ * @param {Error} [error] Optional, an error to emit with 'error' event.
975
+ */
827
976
  leave(error) {
828
977
  this.stop(true);
829
978
  if (!this.isDisconnected) {
@@ -834,9 +983,20 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
834
983
  this.connection.destroy();
835
984
  this.voices.remove(this.id);
836
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
+ */
837
992
  stop(force = false) {
838
993
  this.audioPlayer.stop(force);
839
994
  }
995
+ /**
996
+ * Play a readable stream
997
+ * @private
998
+ * @param {DisTubeStream} stream Readable stream
999
+ */
840
1000
  play(stream) {
841
1001
  this.emittedError = false;
842
1002
  stream.stream.on("error", (error) => {
@@ -850,7 +1010,8 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
850
1010
  inlineVolume: true
851
1011
  });
852
1012
  this.volume = __privateGet(this, _volume);
853
- this.audioPlayer.play(this.audioResource);
1013
+ if (this.audioPlayer.state.status !== import_voice.AudioPlayerStatus.Paused)
1014
+ this.audioPlayer.play(this.audioResource);
854
1015
  }
855
1016
  set volume(volume) {
856
1017
  if (typeof volume !== "number" || isNaN(volume)) {
@@ -865,6 +1026,10 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
865
1026
  get volume() {
866
1027
  return __privateGet(this, _volume);
867
1028
  }
1029
+ /**
1030
+ * Playback duration of the audio resource in seconds
1031
+ * @type {number}
1032
+ */
868
1033
  get playbackDuration() {
869
1034
  return (this.audioResource?.playbackDuration ?? 0) / 1e3;
870
1035
  }
@@ -872,14 +1037,32 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
872
1037
  this.audioPlayer.pause();
873
1038
  }
874
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);
875
1045
  this.audioPlayer.unpause();
876
1046
  }
1047
+ /**
1048
+ * Whether the bot is self-deafened
1049
+ * @type {boolean}
1050
+ */
877
1051
  get selfDeaf() {
878
1052
  return this.connection.joinConfig.selfDeaf;
879
1053
  }
1054
+ /**
1055
+ * Whether the bot is self-muted
1056
+ * @type {boolean}
1057
+ */
880
1058
  get selfMute() {
881
1059
  return this.connection.joinConfig.selfMute;
882
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
+ */
883
1066
  setSelfDeaf(selfDeaf) {
884
1067
  if (typeof selfDeaf !== "boolean") {
885
1068
  throw new DisTubeError("INVALID_TYPE", "boolean", selfDeaf, "selfDeaf");
@@ -889,6 +1072,11 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
889
1072
  selfDeaf
890
1073
  });
891
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
+ */
892
1080
  setSelfMute(selfMute) {
893
1081
  if (typeof selfMute !== "boolean") {
894
1082
  throw new DisTubeError("INVALID_TYPE", "boolean", selfMute, "selfMute");
@@ -898,11 +1086,14 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
898
1086
  selfMute
899
1087
  });
900
1088
  }
1089
+ /**
1090
+ * The voice state of this connection
1091
+ * @type {Discord.VoiceState?}
1092
+ */
901
1093
  get voiceState() {
902
1094
  return this.channel?.guild?.members?.me?.voice;
903
1095
  }
904
1096
  };
905
- __name(DisTubeVoice, "DisTubeVoice");
906
1097
  _channel = new WeakMap();
907
1098
  _volume = new WeakMap();
908
1099
  _br = new WeakSet();
@@ -919,386 +1110,173 @@ join_fn = /* @__PURE__ */ __name(function(channel) {
919
1110
  group: channel.client.user?.id
920
1111
  });
921
1112
  }, "#join");
1113
+ __name(_DisTubeVoice, "DisTubeVoice");
1114
+ var DisTubeVoice = _DisTubeVoice;
922
1115
 
923
- // src/core/manager/BaseManager.ts
924
- var import_discord2 = require("discord.js");
925
- var BaseManager = class extends DisTubeBase {
926
- constructor() {
927
- super(...arguments);
928
- __publicField(this, "collection", new import_discord2.Collection());
929
- }
930
- get size() {
931
- return this.collection.size;
932
- }
933
- };
934
- __name(BaseManager, "BaseManager");
935
-
936
- // src/core/manager/GuildIdManager.ts
937
- var GuildIdManager = class extends BaseManager {
938
- add(idOrInstance, data) {
939
- const id = resolveGuildId(idOrInstance);
940
- const existing = this.get(id);
941
- if (existing)
942
- return this;
943
- return this.collection.set(id, data);
944
- }
945
- get(idOrInstance) {
946
- return this.collection.get(resolveGuildId(idOrInstance));
947
- }
948
- remove(idOrInstance) {
949
- return this.collection.delete(resolveGuildId(idOrInstance));
950
- }
951
- has(idOrInstance) {
952
- return this.collection.has(resolveGuildId(idOrInstance));
953
- }
954
- };
955
- __name(GuildIdManager, "GuildIdManager");
956
-
957
- // src/core/manager/DisTubeVoiceManager.ts
1116
+ // src/core/DisTubeStream.ts
1117
+ var import_prism_media = require("prism-media");
958
1118
  var import_voice2 = require("@discordjs/voice");
959
- var DisTubeVoiceManager = class extends GuildIdManager {
960
- create(channel) {
961
- const existing = this.get(channel.guildId);
962
- if (existing) {
963
- existing.channel = channel;
964
- return existing;
965
- }
966
- return new DisTubeVoice(this, channel);
967
- }
968
- join(channel) {
969
- const existing = this.get(channel.guildId);
970
- if (existing)
971
- return existing.join(channel);
972
- return this.create(channel).join();
973
- }
974
- leave(guild) {
975
- const voice = this.get(guild);
976
- if (voice) {
977
- voice.leave();
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");
978
1160
  } else {
979
- const connection = (0, import_voice2.getVoiceConnection)(resolveGuildId(guild), this.client.user?.id) ?? (0, import_voice2.getVoiceConnection)(resolveGuildId(guild));
980
- if (connection && connection.state.status !== import_voice2.VoiceConnectionStatus.Destroyed) {
981
- connection.destroy();
982
- }
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");
983
1200
  }
1201
+ if (typeof url !== "string" || !isURL(url)) {
1202
+ throw new DisTubeError("INVALID_TYPE", "an URL", url);
1203
+ }
1204
+ return new _DisTubeStream(url, options);
984
1205
  }
985
1206
  };
986
- __name(DisTubeVoiceManager, "DisTubeVoiceManager");
1207
+ __name(_DisTubeStream, "DisTubeStream");
1208
+ var DisTubeStream = _DisTubeStream;
987
1209
 
988
- // src/core/manager/FilterManager.ts
989
- var _validate, validate_fn, _resolveName, resolveName_fn, _resolveValue, resolveValue_fn, _apply, apply_fn;
990
- var FilterManager = class extends BaseManager {
991
- constructor(queue) {
992
- super(queue.distube);
993
- __privateAdd(this, _validate);
994
- __privateAdd(this, _resolveName);
995
- __privateAdd(this, _resolveValue);
996
- __privateAdd(this, _apply);
997
- __publicField(this, "queue");
998
- this.queue = queue;
999
- }
1000
- add(filterOrFilters, override = false) {
1001
- if (Array.isArray(filterOrFilters)) {
1002
- const resolvedFilters = filterOrFilters.map((f) => __privateMethod(this, _validate, validate_fn).call(this, f));
1003
- const newFilters = resolvedFilters.reduceRight((unique, o) => {
1004
- if (!unique.some((obj) => obj === o && obj.name === o) && !unique.some((obj) => obj !== o.name && obj.name !== o.name)) {
1005
- if (!this.has(o))
1006
- unique.push(o);
1007
- if (this.has(o) && override) {
1008
- this.remove(o);
1009
- unique.push(o);
1210
+ // src/core/DisTubeHandler.ts
1211
+ var import_ytpl = __toESM(require("@distube/ytpl"));
1212
+ var import_ytdl_core = __toESM(require("@distube/ytdl-core"));
1213
+ var _DisTubeHandler = class _DisTubeHandler extends DisTubeBase {
1214
+ constructor(distube) {
1215
+ super(distube);
1216
+ const client = this.client;
1217
+ if (this.options.leaveOnEmpty) {
1218
+ client.on("voiceStateUpdate", (oldState) => {
1219
+ if (!oldState?.channel)
1220
+ return;
1221
+ const queue = this.queues.get(oldState);
1222
+ if (!queue) {
1223
+ if (isVoiceChannelEmpty(oldState)) {
1224
+ setTimeout(() => {
1225
+ if (!this.queues.get(oldState) && isVoiceChannelEmpty(oldState))
1226
+ this.voices.leave(oldState);
1227
+ }, this.options.emptyCooldown * 1e3).unref();
1010
1228
  }
1229
+ return;
1011
1230
  }
1012
- return unique;
1013
- }, []).reverse();
1014
- return this.set([...this.collection.values(), ...newFilters]);
1231
+ if (queue._emptyTimeout) {
1232
+ clearTimeout(queue._emptyTimeout);
1233
+ delete queue._emptyTimeout;
1234
+ }
1235
+ if (isVoiceChannelEmpty(oldState)) {
1236
+ queue._emptyTimeout = setTimeout(() => {
1237
+ delete queue._emptyTimeout;
1238
+ if (isVoiceChannelEmpty(oldState)) {
1239
+ queue.voice.leave();
1240
+ this.emit("empty", queue);
1241
+ if (queue.stopped)
1242
+ queue.remove();
1243
+ }
1244
+ }, this.options.emptyCooldown * 1e3).unref();
1245
+ }
1246
+ });
1015
1247
  }
1016
- return this.set([...this.collection.values(), filterOrFilters]);
1017
1248
  }
1018
- clear() {
1019
- return this.set([]);
1020
- }
1021
- set(filters) {
1022
- this.collection.clear();
1023
- for (const filter of filters) {
1024
- const resolved = __privateMethod(this, _validate, validate_fn).call(this, filter);
1025
- this.collection.set(__privateMethod(this, _resolveName, resolveName_fn).call(this, resolved), resolved);
1249
+ get ytdlOptions() {
1250
+ const options = this.options.ytdlOptions;
1251
+ if (this.options.youtubeCookie) {
1252
+ if (!options.requestOptions)
1253
+ options.requestOptions = {};
1254
+ if (!options.requestOptions.headers)
1255
+ options.requestOptions.headers = {};
1256
+ options.requestOptions.headers.cookie = this.options.youtubeCookie;
1257
+ if (this.options.youtubeIdentityToken) {
1258
+ options.requestOptions.headers["x-youtube-identity-token"] = this.options.youtubeIdentityToken;
1259
+ }
1026
1260
  }
1027
- __privateMethod(this, _apply, apply_fn).call(this);
1028
- return this;
1029
- }
1030
- remove(filterOrFilters) {
1031
- const remove = /* @__PURE__ */ __name((f) => this.collection.delete(__privateMethod(this, _resolveName, resolveName_fn).call(this, __privateMethod(this, _validate, validate_fn).call(this, f))), "remove");
1032
- if (Array.isArray(filterOrFilters))
1033
- filterOrFilters.map(remove);
1034
- else
1035
- remove(filterOrFilters);
1036
- __privateMethod(this, _apply, apply_fn).call(this);
1037
- return this;
1038
- }
1039
- has(filter) {
1040
- return this.collection.has(__privateMethod(this, _resolveName, resolveName_fn).call(this, filter));
1041
- }
1042
- get names() {
1043
- return this.collection.map((f) => __privateMethod(this, _resolveName, resolveName_fn).call(this, f));
1044
- }
1045
- get values() {
1046
- return this.collection.map((f) => __privateMethod(this, _resolveValue, resolveValue_fn).call(this, f));
1261
+ return options;
1047
1262
  }
1048
- toString() {
1049
- return this.names.toString();
1050
- }
1051
- };
1052
- __name(FilterManager, "FilterManager");
1053
- _validate = new WeakSet();
1054
- validate_fn = /* @__PURE__ */ __name(function(filter) {
1055
- if (typeof filter === "string" && Object.prototype.hasOwnProperty.call(this.distube.filters, filter) || typeof filter === "object" && typeof filter.name === "string" && typeof filter.value === "string") {
1056
- return filter;
1057
- }
1058
- throw new DisTubeError("INVALID_TYPE", "FilterResolvable", filter, "filter");
1059
- }, "#validate");
1060
- _resolveName = new WeakSet();
1061
- resolveName_fn = /* @__PURE__ */ __name(function(filter) {
1062
- return typeof filter === "string" ? filter : filter.name;
1063
- }, "#resolveName");
1064
- _resolveValue = new WeakSet();
1065
- resolveValue_fn = /* @__PURE__ */ __name(function(filter) {
1066
- return typeof filter === "string" ? this.distube.filters[filter] : filter.value;
1067
- }, "#resolveValue");
1068
- _apply = new WeakSet();
1069
- apply_fn = /* @__PURE__ */ __name(function() {
1070
- this.queue.beginTime = this.queue.currentTime;
1071
- this.queues.playSong(this.queue);
1072
- }, "#apply");
1073
-
1074
- // src/core/manager/QueueManager.ts
1075
- var _voiceEventHandler, voiceEventHandler_fn, _handleSongFinish, handleSongFinish_fn, _handlePlayingError, handlePlayingError_fn, _emitPlaySong, emitPlaySong_fn;
1076
- var QueueManager = class extends GuildIdManager {
1077
- constructor() {
1078
- super(...arguments);
1079
- __privateAdd(this, _voiceEventHandler);
1080
- __privateAdd(this, _handleSongFinish);
1081
- __privateAdd(this, _handlePlayingError);
1082
- __privateAdd(this, _emitPlaySong);
1083
- }
1084
- async create(channel, song, textChannel) {
1085
- if (this.has(channel.guildId))
1086
- throw new DisTubeError("QUEUE_EXIST");
1087
- const voice = this.voices.create(channel);
1088
- const queue = new Queue(this.distube, voice, song, textChannel);
1089
- await queue._taskQueue.queuing();
1090
- try {
1091
- await voice.join();
1092
- __privateMethod(this, _voiceEventHandler, voiceEventHandler_fn).call(this, queue);
1093
- this.add(queue.id, queue);
1094
- this.emit("initQueue", queue);
1095
- const err = await this.playSong(queue);
1096
- return err || queue;
1097
- } finally {
1098
- queue._taskQueue.resolve();
1099
- }
1100
- }
1101
- createStream(queue) {
1102
- const { duration, formats, isLive, source, streamURL } = queue.songs[0];
1103
- const ffmpegArgs = queue.filters.size ? ["-af", queue.filters.values.join(",")] : void 0;
1104
- const seek = duration ? queue.beginTime : void 0;
1105
- const streamOptions = { ffmpegArgs, seek, isLive, type: this.options.streamType };
1106
- if (source === "youtube")
1107
- return DisTubeStream.YouTube(formats, streamOptions);
1108
- return DisTubeStream.DirectLink(streamURL, streamOptions);
1109
- }
1110
- async playSong(queue) {
1111
- if (!queue)
1112
- return true;
1113
- if (!queue.songs.length) {
1114
- queue.stop();
1115
- return true;
1116
- }
1117
- if (queue.stopped)
1118
- return false;
1119
- try {
1120
- const song = queue.songs[0];
1121
- const { url, source, formats, streamURL } = song;
1122
- if (source === "youtube" && !formats)
1123
- song._patchYouTube(await this.handler.getYouTubeInfo(url));
1124
- if (source !== "youtube" && !streamURL) {
1125
- for (const plugin of [...this.distube.extractorPlugins, ...this.distube.customPlugins]) {
1126
- if (await plugin.validate(url)) {
1127
- const info = [plugin.getStreamURL(url), plugin.getRelatedSongs(url)];
1128
- const result = await Promise.all(info);
1129
- song.streamURL = result[0];
1130
- song.related = result[1];
1131
- break;
1132
- }
1133
- }
1134
- }
1135
- const stream = this.createStream(queue);
1136
- queue.voice.play(stream);
1137
- song.streamURL = stream.url;
1138
- if (queue.stopped)
1139
- queue.stop();
1140
- else if (queue.paused)
1141
- queue.voice.pause();
1142
- return false;
1143
- } catch (e) {
1144
- __privateMethod(this, _handlePlayingError, handlePlayingError_fn).call(this, queue, e);
1145
- return true;
1146
- }
1147
- }
1148
- };
1149
- __name(QueueManager, "QueueManager");
1150
- _voiceEventHandler = new WeakSet();
1151
- voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
1152
- queue._listeners = {
1153
- disconnect: (error) => {
1154
- queue.remove();
1155
- this.emit("disconnect", queue);
1156
- if (error)
1157
- this.emitError(error, queue.textChannel);
1158
- },
1159
- error: (error) => __privateMethod(this, _handlePlayingError, handlePlayingError_fn).call(this, queue, error),
1160
- finish: () => __privateMethod(this, _handleSongFinish, handleSongFinish_fn).call(this, queue)
1161
- };
1162
- for (const event of objectKeys(queue._listeners)) {
1163
- queue.voice.on(event, queue._listeners[event]);
1164
- }
1165
- }, "#voiceEventHandler");
1166
- _handleSongFinish = new WeakSet();
1167
- handleSongFinish_fn = /* @__PURE__ */ __name(async function(queue) {
1168
- this.emit("finishSong", queue, queue.songs[0]);
1169
- await queue._taskQueue.queuing();
1170
- try {
1171
- if (queue.stopped)
1172
- return;
1173
- if (queue.repeatMode === 2 /* QUEUE */ && !queue._prev)
1174
- queue.songs.push(queue.songs[0]);
1175
- if (queue._prev) {
1176
- if (queue.repeatMode === 2 /* QUEUE */)
1177
- queue.songs.unshift(queue.songs.pop());
1178
- else
1179
- queue.songs.unshift(queue.previousSongs.pop());
1180
- }
1181
- if (queue.songs.length <= 1 && (queue._next || queue.repeatMode === 0 /* DISABLED */)) {
1182
- if (queue.autoplay) {
1183
- try {
1184
- await queue.addRelatedSong();
1185
- } catch {
1186
- this.emit("noRelated", queue);
1187
- }
1188
- }
1189
- if (queue.songs.length <= 1) {
1190
- if (this.options.leaveOnFinish)
1191
- queue.voice.leave();
1192
- if (!queue.autoplay)
1193
- this.emit("finish", queue);
1194
- queue.remove();
1195
- return;
1196
- }
1197
- }
1198
- const emitPlaySong = __privateMethod(this, _emitPlaySong, emitPlaySong_fn).call(this, queue);
1199
- if (!queue._prev && (queue.repeatMode !== 1 /* SONG */ || queue._next)) {
1200
- const prev = queue.songs.shift();
1201
- delete prev.formats;
1202
- delete prev.streamURL;
1203
- if (this.options.savePreviousSongs)
1204
- queue.previousSongs.push(prev);
1205
- else
1206
- queue.previousSongs.push({ id: prev.id });
1207
- }
1208
- queue._next = queue._prev = false;
1209
- queue.beginTime = 0;
1210
- const err = await this.playSong(queue);
1211
- if (!err && emitPlaySong)
1212
- this.emit("playSong", queue, queue.songs[0]);
1213
- } finally {
1214
- queue._taskQueue.resolve();
1215
- }
1216
- }, "#handleSongFinish");
1217
- _handlePlayingError = new WeakSet();
1218
- handlePlayingError_fn = /* @__PURE__ */ __name(function(queue, error) {
1219
- const song = queue.songs.shift();
1220
- try {
1221
- error.name = "PlayingError";
1222
- error.message = `${error.message}
1223
- Id: ${song.id}
1224
- Name: ${song.name}`;
1225
- } catch {
1226
- }
1227
- this.emitError(error, queue.textChannel);
1228
- if (queue.songs.length > 0) {
1229
- queue._next = queue._prev = false;
1230
- queue.beginTime = 0;
1231
- this.playSong(queue).then((e) => {
1232
- if (!e)
1233
- this.emit("playSong", queue, queue.songs[0]);
1234
- });
1235
- } else {
1236
- queue.stop();
1237
- }
1238
- }, "#handlePlayingError");
1239
- _emitPlaySong = new WeakSet();
1240
- emitPlaySong_fn = /* @__PURE__ */ __name(function(queue) {
1241
- return !this.options.emitNewSongOnly || queue.repeatMode === 1 /* SONG */ && queue._next || queue.repeatMode !== 1 /* SONG */ && queue.songs[0]?.id !== queue.songs[1]?.id;
1242
- }, "#emitPlaySong");
1243
-
1244
- // src/core/DisTubeHandler.ts
1245
- var import_ytpl = __toESM(require("@distube/ytpl"));
1246
- var import_ytdl_core = __toESM(require("@distube/ytdl-core"));
1247
- var DisTubeHandler = class extends DisTubeBase {
1248
- constructor(distube) {
1249
- super(distube);
1250
- const client = this.client;
1251
- if (this.options.leaveOnEmpty) {
1252
- client.on("voiceStateUpdate", (oldState) => {
1253
- if (!oldState?.channel)
1254
- return;
1255
- const queue = this.queues.get(oldState);
1256
- if (!queue) {
1257
- if (isVoiceChannelEmpty(oldState)) {
1258
- setTimeout(() => {
1259
- if (!this.queues.get(oldState) && isVoiceChannelEmpty(oldState))
1260
- this.voices.leave(oldState);
1261
- }, this.options.emptyCooldown * 1e3).unref();
1262
- }
1263
- return;
1264
- }
1265
- if (queue._emptyTimeout) {
1266
- clearTimeout(queue._emptyTimeout);
1267
- delete queue._emptyTimeout;
1268
- }
1269
- if (isVoiceChannelEmpty(oldState)) {
1270
- queue._emptyTimeout = setTimeout(() => {
1271
- delete queue._emptyTimeout;
1272
- if (isVoiceChannelEmpty(oldState)) {
1273
- queue.voice.leave();
1274
- this.emit("empty", queue);
1275
- if (queue.stopped)
1276
- queue.remove();
1277
- }
1278
- }, this.options.emptyCooldown * 1e3).unref();
1279
- }
1280
- });
1281
- }
1282
- }
1283
- get ytdlOptions() {
1284
- const options = this.options.ytdlOptions;
1285
- if (this.options.youtubeCookie) {
1286
- if (!options.requestOptions)
1287
- options.requestOptions = {};
1288
- if (!options.requestOptions.headers)
1289
- options.requestOptions.headers = {};
1290
- options.requestOptions.headers.cookie = this.options.youtubeCookie;
1291
- if (this.options.youtubeIdentityToken) {
1292
- options.requestOptions.headers["x-youtube-identity-token"] = this.options.youtubeIdentityToken;
1293
- }
1294
- }
1295
- return options;
1296
- }
1297
- getYouTubeInfo(url, basic = false) {
1298
- if (basic)
1299
- return import_ytdl_core.default.getBasicInfo(url, this.ytdlOptions);
1300
- return import_ytdl_core.default.getInfo(url, this.ytdlOptions);
1263
+ /**
1264
+ * @param {string} url url
1265
+ * @param {boolean} [basic=false] getBasicInfo?
1266
+ * @returns {Promise<ytdl.videoInfo>}
1267
+ */
1268
+ getYouTubeInfo(url, basic = false) {
1269
+ if (basic)
1270
+ return import_ytdl_core.default.getBasicInfo(url, this.ytdlOptions);
1271
+ return import_ytdl_core.default.getInfo(url, this.ytdlOptions);
1301
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
+ */
1302
1280
  async resolve(song, options = {}) {
1303
1281
  if (song instanceof Song || song instanceof Playlist) {
1304
1282
  if ("metadata" in options)
@@ -1319,7 +1297,7 @@ var DisTubeHandler = class extends DisTubeBase {
1319
1297
  if (import_ytpl.default.validateID(song))
1320
1298
  return this.resolvePlaylist(song, options);
1321
1299
  if (import_ytdl_core.default.validateURL(song))
1322
- return new Song(await this.getYouTubeInfo(song), options);
1300
+ return new Song(await this.getYouTubeInfo(song, true), options);
1323
1301
  if (isURL(song)) {
1324
1302
  for (const plugin of this.distube.extractorPlugins) {
1325
1303
  if (await plugin.validate(song))
@@ -1329,6 +1307,12 @@ var DisTubeHandler = class extends DisTubeBase {
1329
1307
  }
1330
1308
  throw new DisTubeError("CANNOT_RESOLVE_SONG", song);
1331
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
+ */
1332
1316
  async resolvePlaylist(playlist, options = {}) {
1333
1317
  const { member, source, metadata } = { source: "youtube", ...options };
1334
1318
  if (playlist instanceof Playlist) {
@@ -1341,17 +1325,27 @@ var DisTubeHandler = class extends DisTubeBase {
1341
1325
  if (typeof playlist === "string") {
1342
1326
  const info = await (0, import_ytpl.default)(playlist, { limit: Infinity });
1343
1327
  const songs = info.items.filter((v) => !v.thumbnail.includes("no_thumbnail")).map((v) => new Song(v, { member, metadata }));
1344
- return new Playlist({
1345
- source,
1346
- songs,
1347
- member,
1348
- name: info.title,
1349
- url: info.url,
1350
- thumbnail: songs[0].thumbnail
1351
- }, { metadata });
1328
+ return new Playlist(
1329
+ {
1330
+ source,
1331
+ songs,
1332
+ member,
1333
+ name: info.title,
1334
+ url: info.url,
1335
+ thumbnail: songs[0].thumbnail
1336
+ },
1337
+ { metadata }
1338
+ );
1352
1339
  }
1353
1340
  return new Playlist(playlist, { member, properties: { source }, metadata });
1354
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
+ */
1355
1349
  async searchSong(message, query) {
1356
1350
  if (!isMessageInstance(message))
1357
1351
  throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
@@ -1362,7 +1356,7 @@ var DisTubeHandler = class extends DisTubeBase {
1362
1356
  const limit = this.options.searchSongs > 1 ? this.options.searchSongs : 1;
1363
1357
  const results = await this.distube.search(query, {
1364
1358
  limit,
1365
- safeSearch: this.options.nsfw ? false : !message.channel?.nsfw
1359
+ safeSearch: this.options.nsfw ? false : !isNsfwChannel(message.channel)
1366
1360
  }).catch(() => {
1367
1361
  if (!this.emit("searchNoResult", message, query)) {
1368
1362
  console.warn("searchNoResult event does not have any listeners! Emits `error` event instead.");
@@ -1373,6 +1367,17 @@ var DisTubeHandler = class extends DisTubeBase {
1373
1367
  return null;
1374
1368
  return this.createSearchMessageCollector(message, results, query);
1375
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
+ */
1376
1381
  async createSearchMessageCollector(message, results, query) {
1377
1382
  if (!isMessageInstance(message))
1378
1383
  throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
@@ -1390,8 +1395,10 @@ var DisTubeHandler = class extends DisTubeBase {
1390
1395
  for (const evn of searchEvents) {
1391
1396
  if (this.distube.listenerCount(evn) === 0) {
1392
1397
  console.warn(`"searchSongs" option is disabled due to missing "${evn}" listener.`);
1393
- console.warn(`If you don't want to use "${evn}" event, simply add an empty listener (not recommended):
1394
- <DisTube>.on("${evn}", () => {})`);
1398
+ console.warn(
1399
+ `If you don't want to use "${evn}" event, simply add an empty listener (not recommended):
1400
+ <DisTube>.on("${evn}", () => {})`
1401
+ );
1395
1402
  this.options.searchSongs = 0;
1396
1403
  }
1397
1404
  }
@@ -1423,19 +1430,26 @@ var DisTubeHandler = class extends DisTubeBase {
1423
1430
  }
1424
1431
  return result;
1425
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
+ */
1426
1441
  async playPlaylist(voiceChannel, playlist, options = {}) {
1427
1442
  const { textChannel, skip } = { skip: false, ...options };
1428
1443
  const position = Number(options.position) || (skip ? 1 : 0);
1429
1444
  if (!(playlist instanceof Playlist))
1430
1445
  throw new DisTubeError("INVALID_TYPE", "Playlist", playlist, "playlist");
1431
1446
  const queue = this.queues.get(voiceChannel);
1432
- if (!this.options.nsfw && !(queue?.textChannel || textChannel)?.nsfw) {
1447
+ const isNsfw = isNsfwChannel(queue?.textChannel || textChannel);
1448
+ if (!this.options.nsfw && !isNsfw)
1433
1449
  playlist.songs = playlist.songs.filter((s) => !s.age_restricted);
1434
- }
1435
1450
  if (!playlist.songs.length) {
1436
- if (!this.options.nsfw && !textChannel?.nsfw) {
1451
+ if (!this.options.nsfw && !isNsfw)
1437
1452
  throw new DisTubeError("EMPTY_FILTERED_PLAYLIST");
1438
- }
1439
1453
  throw new DisTubeError("EMPTY_PLAYLIST");
1440
1454
  }
1441
1455
  if (queue) {
@@ -1455,13 +1469,21 @@ var DisTubeHandler = class extends DisTubeBase {
1455
1469
  }
1456
1470
  }
1457
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
+ */
1458
1480
  async playSong(voiceChannel, song, options = {}) {
1459
1481
  if (!(song instanceof Song))
1460
1482
  throw new DisTubeError("INVALID_TYPE", "Song", song, "song");
1461
1483
  const { textChannel, skip } = { skip: false, ...options };
1462
1484
  const position = Number(options.position) || (skip ? 1 : 0);
1463
1485
  const queue = this.queues.get(voiceChannel);
1464
- if (!this.options.nsfw && song.age_restricted && !(queue?.textChannel || textChannel)?.nsfw) {
1486
+ if (!this.options.nsfw && song.age_restricted && !isNsfwChannel(queue?.textChannel || textChannel)) {
1465
1487
  throw new DisTubeError("NON_NSFW");
1466
1488
  }
1467
1489
  if (queue) {
@@ -1481,12 +1503,35 @@ var DisTubeHandler = class extends DisTubeBase {
1481
1503
  }
1482
1504
  }
1483
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
+ }
1484
1528
  };
1485
- __name(DisTubeHandler, "DisTubeHandler");
1529
+ __name(_DisTubeHandler, "DisTubeHandler");
1530
+ var DisTubeHandler = _DisTubeHandler;
1486
1531
 
1487
1532
  // src/core/DisTubeOptions.ts
1488
1533
  var _validateOptions, validateOptions_fn;
1489
- var Options = class {
1534
+ var _Options = class _Options {
1490
1535
  constructor(options) {
1491
1536
  __privateAdd(this, _validateOptions);
1492
1537
  __publicField(this, "plugins");
@@ -1535,7 +1580,6 @@ var Options = class {
1535
1580
  __privateMethod(this, _validateOptions, validateOptions_fn).call(this);
1536
1581
  }
1537
1582
  };
1538
- __name(Options, "Options");
1539
1583
  _validateOptions = new WeakSet();
1540
1584
  validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
1541
1585
  if (typeof options.emitNewSongOnly !== "boolean") {
@@ -1554,13 +1598,23 @@ validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
1554
1598
  throw new DisTubeError("INVALID_TYPE", "boolean", options.savePreviousSongs, "DisTubeOptions.savePreviousSongs");
1555
1599
  }
1556
1600
  if (typeof options.joinNewVoiceChannel !== "boolean") {
1557
- throw new DisTubeError("INVALID_TYPE", "boolean", options.joinNewVoiceChannel, "DisTubeOptions.joinNewVoiceChannel");
1601
+ throw new DisTubeError(
1602
+ "INVALID_TYPE",
1603
+ "boolean",
1604
+ options.joinNewVoiceChannel,
1605
+ "DisTubeOptions.joinNewVoiceChannel"
1606
+ );
1558
1607
  }
1559
1608
  if (typeof options.youtubeCookie !== "undefined" && typeof options.youtubeCookie !== "string") {
1560
1609
  throw new DisTubeError("INVALID_TYPE", "string", options.youtubeCookie, "DisTubeOptions.youtubeCookie");
1561
1610
  }
1562
1611
  if (typeof options.youtubeIdentityToken !== "undefined" && typeof options.youtubeIdentityToken !== "string") {
1563
- throw new DisTubeError("INVALID_TYPE", "string", options.youtubeIdentityToken, "DisTubeOptions.youtubeIdentityToken");
1612
+ throw new DisTubeError(
1613
+ "INVALID_TYPE",
1614
+ "string",
1615
+ options.youtubeIdentityToken,
1616
+ "DisTubeOptions.youtubeIdentityToken"
1617
+ );
1564
1618
  }
1565
1619
  if (typeof options.customFilters !== "undefined" && typeof options.customFilters !== "object" || Array.isArray(options.customFilters)) {
1566
1620
  throw new DisTubeError("INVALID_TYPE", "object", options.customFilters, "DisTubeOptions.customFilters");
@@ -1568,110 +1622,514 @@ validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
1568
1622
  if (typeof options.ytdlOptions !== "object" || Array.isArray(options.ytdlOptions)) {
1569
1623
  throw new DisTubeError("INVALID_TYPE", "object", options.ytdlOptions, "DisTubeOptions.ytdlOptions");
1570
1624
  }
1571
- if (typeof options.searchCooldown !== "number" || isNaN(options.searchCooldown)) {
1572
- throw new DisTubeError("INVALID_TYPE", "number", options.searchCooldown, "DisTubeOptions.searchCooldown");
1625
+ if (typeof options.searchCooldown !== "number" || isNaN(options.searchCooldown)) {
1626
+ throw new DisTubeError("INVALID_TYPE", "number", options.searchCooldown, "DisTubeOptions.searchCooldown");
1627
+ }
1628
+ if (typeof options.emptyCooldown !== "number" || isNaN(options.emptyCooldown)) {
1629
+ throw new DisTubeError("INVALID_TYPE", "number", options.emptyCooldown, "DisTubeOptions.emptyCooldown");
1630
+ }
1631
+ if (typeof options.searchSongs !== "number" || isNaN(options.searchSongs)) {
1632
+ throw new DisTubeError("INVALID_TYPE", "number", options.searchSongs, "DisTubeOptions.searchSongs");
1633
+ }
1634
+ if (!Array.isArray(options.plugins)) {
1635
+ throw new DisTubeError("INVALID_TYPE", "Array<Plugin>", options.plugins, "DisTubeOptions.plugins");
1636
+ }
1637
+ if (typeof options.nsfw !== "boolean") {
1638
+ throw new DisTubeError("INVALID_TYPE", "boolean", options.nsfw, "DisTubeOptions.nsfw");
1639
+ }
1640
+ if (typeof options.emitAddSongWhenCreatingQueue !== "boolean") {
1641
+ throw new DisTubeError(
1642
+ "INVALID_TYPE",
1643
+ "boolean",
1644
+ options.emitAddSongWhenCreatingQueue,
1645
+ "DisTubeOptions.emitAddSongWhenCreatingQueue"
1646
+ );
1647
+ }
1648
+ if (typeof options.emitAddListWhenCreatingQueue !== "boolean") {
1649
+ throw new DisTubeError(
1650
+ "INVALID_TYPE",
1651
+ "boolean",
1652
+ options.emitAddListWhenCreatingQueue,
1653
+ "DisTubeOptions.emitAddListWhenCreatingQueue"
1654
+ );
1655
+ }
1656
+ if (typeof options.streamType !== "number" || isNaN(options.streamType) || !StreamType[options.streamType]) {
1657
+ throw new DisTubeError("INVALID_TYPE", "StreamType", options.streamType, "DisTubeOptions.streamType");
1658
+ }
1659
+ if (typeof options.directLink !== "boolean") {
1660
+ throw new DisTubeError("INVALID_TYPE", "boolean", options.directLink, "DisTubeOptions.directLink");
1661
+ }
1662
+ }, "#validateOptions");
1663
+ __name(_Options, "Options");
1664
+ var Options = _Options;
1665
+
1666
+ // src/core/manager/BaseManager.ts
1667
+ var import_discord2 = require("discord.js");
1668
+ var _BaseManager = class _BaseManager extends DisTubeBase {
1669
+ constructor() {
1670
+ super(...arguments);
1671
+ /**
1672
+ * The collection of items for this manager.
1673
+ * @type {Collection}
1674
+ * @name BaseManager#collection
1675
+ */
1676
+ __publicField(this, "collection", new import_discord2.Collection());
1677
+ }
1678
+ /**
1679
+ * The size of the collection.
1680
+ * @type {number}
1681
+ */
1682
+ get size() {
1683
+ return this.collection.size;
1684
+ }
1685
+ };
1686
+ __name(_BaseManager, "BaseManager");
1687
+ var BaseManager = _BaseManager;
1688
+
1689
+ // src/core/manager/GuildIdManager.ts
1690
+ var _GuildIdManager = class _GuildIdManager extends BaseManager {
1691
+ add(idOrInstance, data) {
1692
+ const id = resolveGuildId(idOrInstance);
1693
+ const existing = this.get(id);
1694
+ if (existing)
1695
+ return this;
1696
+ return this.collection.set(id, data);
1697
+ }
1698
+ get(idOrInstance) {
1699
+ return this.collection.get(resolveGuildId(idOrInstance));
1700
+ }
1701
+ remove(idOrInstance) {
1702
+ return this.collection.delete(resolveGuildId(idOrInstance));
1703
+ }
1704
+ has(idOrInstance) {
1705
+ return this.collection.has(resolveGuildId(idOrInstance));
1706
+ }
1707
+ };
1708
+ __name(_GuildIdManager, "GuildIdManager");
1709
+ var GuildIdManager = _GuildIdManager;
1710
+
1711
+ // src/core/manager/DisTubeVoiceManager.ts
1712
+ var import_voice3 = require("@discordjs/voice");
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
+ */
1732
+ create(channel) {
1733
+ const existing = this.get(channel.guildId);
1734
+ if (existing) {
1735
+ existing.channel = channel;
1736
+ return existing;
1737
+ }
1738
+ return new DisTubeVoice(this, channel);
1739
+ }
1740
+ /**
1741
+ * Join a voice channel
1742
+ * @param {Discord.BaseGuildVoiceChannel} channel A voice channel to join
1743
+ * @returns {Promise<DisTubeVoice>}
1744
+ */
1745
+ join(channel) {
1746
+ const existing = this.get(channel.guildId);
1747
+ if (existing)
1748
+ return existing.join(channel);
1749
+ return this.create(channel).join();
1750
+ }
1751
+ /**
1752
+ * Leave the connected voice channel in a guild
1753
+ * @param {GuildIdResolvable} guild Queue Resolvable
1754
+ */
1755
+ leave(guild) {
1756
+ const voice = this.get(guild);
1757
+ if (voice) {
1758
+ voice.leave();
1759
+ } else {
1760
+ const connection = (0, import_voice3.getVoiceConnection)(resolveGuildId(guild), this.client.user?.id) ?? (0, import_voice3.getVoiceConnection)(resolveGuildId(guild));
1761
+ if (connection && connection.state.status !== import_voice3.VoiceConnectionStatus.Destroyed) {
1762
+ connection.destroy();
1763
+ }
1764
+ }
1765
+ }
1766
+ };
1767
+ __name(_DisTubeVoiceManager, "DisTubeVoiceManager");
1768
+ var DisTubeVoiceManager = _DisTubeVoiceManager;
1769
+
1770
+ // src/core/manager/FilterManager.ts
1771
+ var _validate, validate_fn, _resolveName, resolveName_fn, _resolveValue, resolveValue_fn, _apply, apply_fn, _removeFn, removeFn_get;
1772
+ var _FilterManager = class _FilterManager extends BaseManager {
1773
+ constructor(queue) {
1774
+ super(queue.distube);
1775
+ __privateAdd(this, _validate);
1776
+ __privateAdd(this, _resolveName);
1777
+ __privateAdd(this, _resolveValue);
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
+ */
1785
+ __publicField(this, "queue");
1786
+ this.queue = queue;
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
+ */
1794
+ add(filterOrFilters, override = false) {
1795
+ if (Array.isArray(filterOrFilters)) {
1796
+ const resolvedFilters = filterOrFilters.map((f) => __privateMethod(this, _validate, validate_fn).call(this, f));
1797
+ const newFilters = resolvedFilters.reduceRight((unique, o) => {
1798
+ if (!unique.some((obj) => obj === o && obj.name === o) && !unique.some((obj) => obj !== o.name && obj.name !== o.name)) {
1799
+ if (!this.has(o))
1800
+ unique.push(o);
1801
+ if (this.has(o) && override) {
1802
+ this.remove(o);
1803
+ unique.push(o);
1804
+ }
1805
+ }
1806
+ return unique;
1807
+ }, []).reverse();
1808
+ return this.set([...this.collection.values(), ...newFilters]);
1809
+ } else if (typeof filterOrFilters === "string") {
1810
+ return this.set([...this.collection.values(), filterOrFilters]);
1811
+ }
1812
+ throw new DisTubeError(
1813
+ "INVALID_TYPE",
1814
+ ["FilterResolvable", "Array<FilterResolvable>"],
1815
+ filterOrFilters,
1816
+ "filterOrFilters"
1817
+ );
1818
+ }
1819
+ /**
1820
+ * Clear enabled filters of the manager
1821
+ * @returns {FilterManager}
1822
+ */
1823
+ clear() {
1824
+ return this.set([]);
1825
+ }
1826
+ /**
1827
+ * Set the filters applied to the manager
1828
+ * @param {FilterResolvable[]} filters The filters to apply
1829
+ * @returns {FilterManager}
1830
+ */
1831
+ set(filters) {
1832
+ if (!Array.isArray(filters))
1833
+ throw new DisTubeError("INVALID_TYPE", "Array<FilterResolvable>", filters, "filters");
1834
+ this.collection.clear();
1835
+ for (const filter of filters) {
1836
+ const resolved = __privateMethod(this, _validate, validate_fn).call(this, filter);
1837
+ this.collection.set(__privateMethod(this, _resolveName, resolveName_fn).call(this, resolved), resolved);
1838
+ }
1839
+ __privateMethod(this, _apply, apply_fn).call(this);
1840
+ return this;
1841
+ }
1842
+ /**
1843
+ * Disable a filter or multiple filters
1844
+ * @param {FilterResolvable|FilterResolvable[]} filterOrFilters The filter or filters to disable
1845
+ * @returns {FilterManager}
1846
+ */
1847
+ 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
+ }
1860
+ __privateMethod(this, _apply, apply_fn).call(this);
1861
+ return this;
1573
1862
  }
1574
- if (typeof options.emptyCooldown !== "number" || isNaN(options.emptyCooldown)) {
1575
- throw new DisTubeError("INVALID_TYPE", "number", options.emptyCooldown, "DisTubeOptions.emptyCooldown");
1863
+ /**
1864
+ * Check whether a filter enabled or not
1865
+ * @param {FilterResolvable} filter The filter to check
1866
+ * @returns {boolean}
1867
+ */
1868
+ has(filter) {
1869
+ return this.collection.has(__privateMethod(this, _resolveName, resolveName_fn).call(this, filter));
1576
1870
  }
1577
- if (typeof options.searchSongs !== "number" || isNaN(options.searchSongs)) {
1578
- throw new DisTubeError("INVALID_TYPE", "number", options.searchSongs, "DisTubeOptions.searchSongs");
1871
+ /**
1872
+ * Array of enabled filter name
1873
+ * @type {Array<string>}
1874
+ * @readonly
1875
+ */
1876
+ get names() {
1877
+ return this.collection.map((f) => __privateMethod(this, _resolveName, resolveName_fn).call(this, f));
1579
1878
  }
1580
- if (!Array.isArray(options.plugins)) {
1581
- throw new DisTubeError("INVALID_TYPE", "Array<Plugin>", options.plugins, "DisTubeOptions.plugins");
1879
+ get values() {
1880
+ return this.collection.map((f) => __privateMethod(this, _resolveValue, resolveValue_fn).call(this, f));
1582
1881
  }
1583
- if (typeof options.nsfw !== "boolean") {
1584
- throw new DisTubeError("INVALID_TYPE", "boolean", options.nsfw, "DisTubeOptions.nsfw");
1882
+ toString() {
1883
+ return this.names.toString();
1585
1884
  }
1586
- if (typeof options.emitAddSongWhenCreatingQueue !== "boolean") {
1587
- throw new DisTubeError("INVALID_TYPE", "boolean", options.emitAddSongWhenCreatingQueue, "DisTubeOptions.emitAddSongWhenCreatingQueue");
1885
+ };
1886
+ _validate = new WeakSet();
1887
+ validate_fn = /* @__PURE__ */ __name(function(filter) {
1888
+ if (typeof filter === "string" && Object.prototype.hasOwnProperty.call(this.distube.filters, filter) || typeof filter === "object" && typeof filter.name === "string" && typeof filter.value === "string") {
1889
+ return filter;
1588
1890
  }
1589
- if (typeof options.emitAddListWhenCreatingQueue !== "boolean") {
1590
- throw new DisTubeError("INVALID_TYPE", "boolean", options.emitAddListWhenCreatingQueue, "DisTubeOptions.emitAddListWhenCreatingQueue");
1891
+ throw new DisTubeError("INVALID_TYPE", "FilterResolvable", filter, "filter");
1892
+ }, "#validate");
1893
+ _resolveName = new WeakSet();
1894
+ resolveName_fn = /* @__PURE__ */ __name(function(filter) {
1895
+ return typeof filter === "string" ? filter : filter.name;
1896
+ }, "#resolveName");
1897
+ _resolveValue = new WeakSet();
1898
+ resolveValue_fn = /* @__PURE__ */ __name(function(filter) {
1899
+ return typeof filter === "string" ? this.distube.filters[filter] : filter.value;
1900
+ }, "#resolveValue");
1901
+ _apply = new WeakSet();
1902
+ apply_fn = /* @__PURE__ */ __name(function() {
1903
+ this.queue.beginTime = this.queue.currentTime;
1904
+ this.queues.playSong(this.queue);
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;
1912
+
1913
+ // src/core/manager/QueueManager.ts
1914
+ var _voiceEventHandler, voiceEventHandler_fn, _emitPlaySong, emitPlaySong_fn, _handleSongFinish, handleSongFinish_fn, _handlePlayingError, handlePlayingError_fn;
1915
+ var _QueueManager = class _QueueManager extends GuildIdManager {
1916
+ constructor() {
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
+ */
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
+ */
1944
+ __privateAdd(this, _handleSongFinish);
1945
+ /**
1946
+ * Handle error while playing
1947
+ * @private
1948
+ * @param {Queue} queue queue
1949
+ * @param {Error} error error
1950
+ */
1951
+ __privateAdd(this, _handlePlayingError);
1591
1952
  }
1592
- if (typeof options.streamType !== "number" || isNaN(options.streamType) || !StreamType[options.streamType]) {
1593
- throw new DisTubeError("INVALID_TYPE", "StreamType", options.streamType, "DisTubeOptions.streamType");
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
+ */
1966
+ async create(channel, song, textChannel) {
1967
+ if (this.has(channel.guildId))
1968
+ throw new DisTubeError("QUEUE_EXIST");
1969
+ const voice = this.voices.create(channel);
1970
+ const queue = new Queue(this.distube, voice, song, textChannel);
1971
+ await queue._taskQueue.queuing();
1972
+ try {
1973
+ await voice.join();
1974
+ __privateMethod(this, _voiceEventHandler, voiceEventHandler_fn).call(this, queue);
1975
+ this.add(queue.id, queue);
1976
+ this.emit("initQueue", queue);
1977
+ const err = await this.playSong(queue);
1978
+ return err || queue;
1979
+ } finally {
1980
+ queue._taskQueue.resolve();
1981
+ }
1594
1982
  }
1595
- if (typeof options.directLink !== "boolean") {
1596
- throw new DisTubeError("INVALID_TYPE", "boolean", options.directLink, "DisTubeOptions.directLink");
1983
+ /**
1984
+ * Create a ytdl stream
1985
+ * @param {Queue} queue Queue
1986
+ * @returns {DisTubeStream}
1987
+ */
1988
+ createStream(queue) {
1989
+ const { duration, formats, isLive, source, streamURL } = queue.songs[0];
1990
+ const ffmpegArgs = queue.filters.size ? ["-af", queue.filters.values.join(",")] : void 0;
1991
+ const seek = duration ? queue.beginTime : void 0;
1992
+ const streamOptions = { ffmpegArgs, seek, isLive, type: this.options.streamType };
1993
+ if (source === "youtube")
1994
+ return DisTubeStream.YouTube(formats, streamOptions);
1995
+ return DisTubeStream.DirectLink(streamURL, streamOptions);
1597
1996
  }
1598
- }, "#validateOptions");
1599
-
1600
- // src/core/DisTubeStream.ts
1601
- var import_prism_media = require("prism-media");
1602
- var import_voice3 = require("@discordjs/voice");
1603
- var chooseBestVideoFormat = /* @__PURE__ */ __name((formats, isLive = false) => {
1604
- let filter = /* @__PURE__ */ __name((format) => format.hasAudio, "filter");
1605
- if (isLive)
1606
- filter = /* @__PURE__ */ __name((format) => format.hasAudio && format.isHLS, "filter");
1607
- formats = formats.filter(filter).sort((a, b) => Number(b.audioBitrate) - Number(a.audioBitrate) || Number(a.bitrate) - Number(b.bitrate));
1608
- return formats.find((format) => !format.hasVideo) || formats.sort((a, b) => Number(a.bitrate) - Number(b.bitrate))[0];
1609
- }, "chooseBestVideoFormat");
1610
- var DisTubeStream = class {
1611
- constructor(url, options) {
1612
- __publicField(this, "type");
1613
- __publicField(this, "stream");
1614
- __publicField(this, "url");
1615
- this.url = url;
1616
- this.type = !options.type ? import_voice3.StreamType.OggOpus : import_voice3.StreamType.Raw;
1617
- const args = [
1618
- "-reconnect",
1619
- "1",
1620
- "-reconnect_streamed",
1621
- "1",
1622
- "-reconnect_delay_max",
1623
- "5",
1624
- "-i",
1625
- url,
1626
- "-analyzeduration",
1627
- "0",
1628
- "-loglevel",
1629
- "0",
1630
- "-ar",
1631
- "48000",
1632
- "-ac",
1633
- "2",
1634
- "-f"
1635
- ];
1636
- if (!options.type) {
1637
- args.push("opus", "-acodec", "libopus");
1638
- } else {
1639
- args.push("s16le");
1640
- }
1641
- if (typeof options.seek === "number" && options.seek > 0) {
1642
- args.unshift("-ss", options.seek.toString());
1997
+ /**
1998
+ * Play a song on voice connection
1999
+ * @private
2000
+ * @param {Queue} queue The guild queue
2001
+ * @returns {Promise<boolean>} error?
2002
+ */
2003
+ async playSong(queue) {
2004
+ if (!queue)
2005
+ return true;
2006
+ if (queue.stopped || !queue.songs.length) {
2007
+ queue.stop();
2008
+ return true;
1643
2009
  }
1644
- if (Array.isArray(options.ffmpegArgs)) {
1645
- args.push(...options.ffmpegArgs);
2010
+ try {
2011
+ const song = queue.songs[0];
2012
+ await this.handler.attachStreamInfo(song);
2013
+ if (queue.stopped || !queue.songs.length) {
2014
+ queue.stop();
2015
+ return true;
2016
+ }
2017
+ const stream = this.createStream(queue);
2018
+ queue.voice.play(stream);
2019
+ song.streamURL = stream.url;
2020
+ return false;
2021
+ } catch (e) {
2022
+ __privateMethod(this, _handlePlayingError, handlePlayingError_fn).call(this, queue, e);
2023
+ return true;
1646
2024
  }
1647
- this.stream = new import_prism_media.FFmpeg({ args, shell: false });
1648
2025
  }
1649
- static YouTube(formats, options = {}) {
1650
- if (!formats || !formats.length)
1651
- throw new DisTubeError("UNAVAILABLE_VIDEO");
1652
- if (!options || typeof options !== "object" || Array.isArray(options)) {
1653
- throw new DisTubeError("INVALID_TYPE", "object", options, "options");
1654
- }
1655
- const bestFormat = chooseBestVideoFormat(formats, options.isLive);
1656
- if (!bestFormat)
1657
- throw new DisTubeError("UNPLAYABLE_FORMATS");
1658
- return new DisTubeStream(bestFormat.url, options);
2026
+ };
2027
+ _voiceEventHandler = new WeakSet();
2028
+ voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
2029
+ queue._listeners = {
2030
+ disconnect: (error) => {
2031
+ queue.remove();
2032
+ this.emit("disconnect", queue);
2033
+ if (error)
2034
+ this.emitError(error, queue.textChannel);
2035
+ },
2036
+ error: (error) => __privateMethod(this, _handlePlayingError, handlePlayingError_fn).call(this, queue, error),
2037
+ finish: () => __privateMethod(this, _handleSongFinish, handleSongFinish_fn).call(this, queue)
2038
+ };
2039
+ for (const event of objectKeys(queue._listeners)) {
2040
+ queue.voice.on(event, queue._listeners[event]);
1659
2041
  }
1660
- static DirectLink(url, options = {}) {
1661
- if (!options || typeof options !== "object" || Array.isArray(options)) {
1662
- throw new DisTubeError("INVALID_TYPE", "object", options, "options");
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");
2047
+ _handleSongFinish = new WeakSet();
2048
+ handleSongFinish_fn = /* @__PURE__ */ __name(async function(queue) {
2049
+ this.emit("finishSong", queue, queue.songs[0]);
2050
+ await queue._taskQueue.queuing();
2051
+ try {
2052
+ if (queue.stopped)
2053
+ return;
2054
+ if (queue.repeatMode === 2 /* QUEUE */ && !queue._prev)
2055
+ queue.songs.push(queue.songs[0]);
2056
+ if (queue._prev) {
2057
+ if (queue.repeatMode === 2 /* QUEUE */)
2058
+ queue.songs.unshift(queue.songs.pop());
2059
+ else
2060
+ queue.songs.unshift(queue.previousSongs.pop());
1663
2061
  }
1664
- if (typeof url !== "string" || !isURL(url)) {
1665
- throw new DisTubeError("INVALID_TYPE", "an URL", url);
2062
+ if (queue.songs.length <= 1 && (queue._next || queue.repeatMode === 0 /* DISABLED */)) {
2063
+ if (queue.autoplay) {
2064
+ try {
2065
+ await queue.addRelatedSong();
2066
+ } catch {
2067
+ this.emit("noRelated", queue);
2068
+ }
2069
+ }
2070
+ if (queue.songs.length <= 1) {
2071
+ if (this.options.leaveOnFinish)
2072
+ queue.voice.leave();
2073
+ if (!queue.autoplay)
2074
+ this.emit("finish", queue);
2075
+ queue.remove();
2076
+ return;
2077
+ }
2078
+ }
2079
+ const emitPlaySong = __privateMethod(this, _emitPlaySong, emitPlaySong_fn).call(this, queue);
2080
+ if (!queue._prev && (queue.repeatMode !== 1 /* SONG */ || queue._next)) {
2081
+ const prev = queue.songs.shift();
2082
+ delete prev.formats;
2083
+ delete prev.streamURL;
2084
+ if (this.options.savePreviousSongs)
2085
+ queue.previousSongs.push(prev);
2086
+ else
2087
+ queue.previousSongs.push({ id: prev.id });
1666
2088
  }
1667
- return new DisTubeStream(url, options);
2089
+ queue._next = queue._prev = false;
2090
+ queue.beginTime = 0;
2091
+ const err = await this.playSong(queue);
2092
+ if (!err && emitPlaySong)
2093
+ this.emit("playSong", queue, queue.songs[0]);
2094
+ } finally {
2095
+ queue._taskQueue.resolve();
1668
2096
  }
1669
- };
1670
- __name(DisTubeStream, "DisTubeStream");
2097
+ }, "#handleSongFinish");
2098
+ _handlePlayingError = new WeakSet();
2099
+ handlePlayingError_fn = /* @__PURE__ */ __name(function(queue, error) {
2100
+ const song = queue.songs.shift();
2101
+ try {
2102
+ error.name = "PlayingError";
2103
+ error.message = `${error.message}
2104
+ Id: ${song.id}
2105
+ Name: ${song.name}`;
2106
+ } catch {
2107
+ }
2108
+ this.emitError(error, queue.textChannel);
2109
+ if (queue.songs.length > 0) {
2110
+ queue._next = queue._prev = false;
2111
+ queue.beginTime = 0;
2112
+ this.playSong(queue).then((e) => {
2113
+ if (!e)
2114
+ this.emit("playSong", queue, queue.songs[0]);
2115
+ });
2116
+ } else {
2117
+ queue.stop();
2118
+ }
2119
+ }, "#handlePlayingError");
2120
+ __name(_QueueManager, "QueueManager");
2121
+ var QueueManager = _QueueManager;
1671
2122
 
1672
2123
  // src/struct/Queue.ts
1673
2124
  var _filters;
1674
- 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
+ */
1675
2133
  constructor(distube, voice, song, textChannel) {
1676
2134
  super(distube);
1677
2135
  __publicField(this, "id");
@@ -1710,24 +2168,58 @@ var Queue = class extends DisTubeBase {
1710
2168
  this._taskQueue = new TaskQueue();
1711
2169
  this._listeners = void 0;
1712
2170
  }
2171
+ /**
2172
+ * The client user as a `GuildMember` of this queue's guild
2173
+ * @type {Discord.GuildMember?}
2174
+ */
1713
2175
  get clientMember() {
1714
2176
  return this.voice.channel.guild.members.me ?? void 0;
1715
2177
  }
2178
+ /**
2179
+ * The filter manager of the queue
2180
+ * @type {FilterManager}
2181
+ * @readonly
2182
+ */
1716
2183
  get filters() {
1717
2184
  return __privateGet(this, _filters);
1718
2185
  }
2186
+ /**
2187
+ * Formatted duration string.
2188
+ * @type {string}
2189
+ * @readonly
2190
+ */
1719
2191
  get formattedDuration() {
1720
2192
  return formatDuration(this.duration);
1721
2193
  }
2194
+ /**
2195
+ * Queue's duration.
2196
+ * @type {number}
2197
+ * @readonly
2198
+ */
1722
2199
  get duration() {
1723
2200
  return this.songs.length ? this.songs.reduce((prev, next) => prev + next.duration, 0) : 0;
1724
2201
  }
2202
+ /**
2203
+ * What time in the song is playing (in seconds).
2204
+ * @type {number}
2205
+ * @readonly
2206
+ */
1725
2207
  get currentTime() {
1726
2208
  return this.voice.playbackDuration + this.beginTime;
1727
2209
  }
2210
+ /**
2211
+ * Formatted {@link Queue#currentTime} string.
2212
+ * @type {string}
2213
+ * @readonly
2214
+ */
1728
2215
  get formattedCurrentTime() {
1729
2216
  return formatDuration(this.currentTime);
1730
2217
  }
2218
+ /**
2219
+ * The voice channel playing in.
2220
+ * @type {Discord.VoiceChannel|Discord.StageChannel|null}
2221
+ * @readonly
2222
+ */
1731
2223
  get voiceChannel() {
1732
2224
  return this.clientMember?.voice?.channel ?? null;
1733
2225
  }
@@ -1737,6 +2229,14 @@ var Queue = class extends DisTubeBase {
1737
2229
  set volume(value) {
1738
2230
  this.voice.volume = value;
1739
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
+ */
1740
2240
  addToQueue(song, position = 0) {
1741
2241
  if (!song || Array.isArray(song) && !song.length) {
1742
2242
  throw new DisTubeError("INVALID_TYPE", ["Song", "Array<Song>"], song, "song");
@@ -1760,6 +2260,10 @@ var Queue = class extends DisTubeBase {
1760
2260
  delete song.formats;
1761
2261
  return this;
1762
2262
  }
2263
+ /**
2264
+ * Pause the guild stream
2265
+ * @returns {Queue} The guild queue
2266
+ */
1763
2267
  pause() {
1764
2268
  if (this.paused)
1765
2269
  throw new DisTubeError("PAUSED");
@@ -1768,6 +2272,10 @@ var Queue = class extends DisTubeBase {
1768
2272
  this.voice.pause();
1769
2273
  return this;
1770
2274
  }
2275
+ /**
2276
+ * Resume the guild stream
2277
+ * @returns {Queue} The guild queue
2278
+ */
1771
2279
  resume() {
1772
2280
  if (this.playing)
1773
2281
  throw new DisTubeError("RESUMED");
@@ -1776,10 +2284,22 @@ var Queue = class extends DisTubeBase {
1776
2284
  this.voice.unpause();
1777
2285
  return this;
1778
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
+ */
1779
2292
  setVolume(percent) {
1780
2293
  this.volume = percent;
1781
2294
  return this;
1782
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
+ */
1783
2303
  async skip() {
1784
2304
  await this._taskQueue.queuing();
1785
2305
  try {
@@ -1797,6 +2317,11 @@ var Queue = class extends DisTubeBase {
1797
2317
  this._taskQueue.resolve();
1798
2318
  }
1799
2319
  }
2320
+ /**
2321
+ * Play the previous song if exists
2322
+ * @returns {Promise<Song>} The guild queue
2323
+ * @throws {Error}
2324
+ */
1800
2325
  async previous() {
1801
2326
  await this._taskQueue.queuing();
1802
2327
  try {
@@ -1813,6 +2338,10 @@ var Queue = class extends DisTubeBase {
1813
2338
  this._taskQueue.resolve();
1814
2339
  }
1815
2340
  }
2341
+ /**
2342
+ * Shuffle the queue's songs
2343
+ * @returns {Promise<Queue>} The guild queue
2344
+ */
1816
2345
  async shuffle() {
1817
2346
  await this._taskQueue.queuing();
1818
2347
  try {
@@ -1829,6 +2358,14 @@ var Queue = class extends DisTubeBase {
1829
2358
  this._taskQueue.resolve();
1830
2359
  }
1831
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
+ */
1832
2369
  async jump(position) {
1833
2370
  await this._taskQueue.queuing();
1834
2371
  try {
@@ -1862,6 +2399,12 @@ var Queue = class extends DisTubeBase {
1862
2399
  this._taskQueue.resolve();
1863
2400
  }
1864
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
+ */
1865
2408
  setRepeatMode(mode) {
1866
2409
  if (mode !== void 0 && !Object.values(RepeatMode).includes(mode)) {
1867
2410
  throw new DisTubeError("INVALID_TYPE", ["RepeatMode", "undefined"], mode, "mode");
@@ -1874,6 +2417,11 @@ var Queue = class extends DisTubeBase {
1874
2417
  this.repeatMode = mode;
1875
2418
  return this.repeatMode;
1876
2419
  }
2420
+ /**
2421
+ * Set the playing time to another position
2422
+ * @param {number} time Time in seconds
2423
+ * @returns {Queue} The guild queue
2424
+ */
1877
2425
  seek(time) {
1878
2426
  if (typeof time !== "number")
1879
2427
  throw new DisTubeError("INVALID_TYPE", "number", time, "time");
@@ -1883,6 +2431,11 @@ var Queue = class extends DisTubeBase {
1883
2431
  this.queues.playSong(this);
1884
2432
  return this;
1885
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
+ */
1886
2439
  async addRelatedSong() {
1887
2440
  if (!this.songs?.[0])
1888
2441
  throw new DisTubeError("NO_PLAYING");
@@ -1895,6 +2448,9 @@ var Queue = class extends DisTubeBase {
1895
2448
  this.addToQueue(song);
1896
2449
  return song;
1897
2450
  }
2451
+ /**
2452
+ * Stop the guild stream and delete the queue
2453
+ */
1898
2454
  async stop() {
1899
2455
  await this._taskQueue.queuing();
1900
2456
  try {
@@ -1910,6 +2466,11 @@ var Queue = class extends DisTubeBase {
1910
2466
  this._taskQueue.resolve();
1911
2467
  }
1912
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
+ */
1913
2474
  remove() {
1914
2475
  this.stopped = true;
1915
2476
  this.songs = [];
@@ -1922,72 +2483,138 @@ var Queue = class extends DisTubeBase {
1922
2483
  this.queues.remove(this.id);
1923
2484
  this.emit("deleteQueue", this);
1924
2485
  }
2486
+ /**
2487
+ * Toggle autoplay mode
2488
+ * @returns {boolean} Autoplay mode state
2489
+ */
1925
2490
  toggleAutoplay() {
1926
2491
  this.autoplay = !this.autoplay;
1927
2492
  return this.autoplay;
1928
2493
  }
1929
2494
  };
1930
- __name(Queue, "Queue");
1931
2495
  _filters = new WeakMap();
2496
+ __name(_Queue, "Queue");
2497
+ var Queue = _Queue;
1932
2498
 
1933
2499
  // src/struct/Plugin.ts
1934
- var Plugin = class {
2500
+ var _Plugin = class _Plugin {
1935
2501
  constructor() {
1936
2502
  __publicField(this, "distube");
1937
2503
  }
1938
2504
  init(distube) {
1939
2505
  this.distube = distube;
1940
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
+ */
1941
2518
  emit(eventName, ...args) {
1942
2519
  return this.distube.emit(eventName, ...args);
1943
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
+ */
1944
2526
  emitError(error, channel) {
1945
2527
  this.distube.emitError(error, channel);
1946
2528
  }
2529
+ /**
2530
+ * The queue manager
2531
+ * @type {QueueManager}
2532
+ * @readonly
2533
+ */
1947
2534
  get queues() {
1948
2535
  return this.distube.queues;
1949
2536
  }
2537
+ /**
2538
+ * The voice manager
2539
+ * @type {DisTubeVoiceManager}
2540
+ * @readonly
2541
+ */
1950
2542
  get voices() {
1951
2543
  return this.distube.voices;
1952
2544
  }
2545
+ /**
2546
+ * Discord.js client
2547
+ * @type {Discord.Client}
2548
+ * @readonly
2549
+ */
1953
2550
  get client() {
1954
2551
  return this.distube.client;
1955
2552
  }
2553
+ /**
2554
+ * DisTube options
2555
+ * @type {DisTubeOptions}
2556
+ * @readonly
2557
+ */
1956
2558
  get options() {
1957
2559
  return this.distube.options;
1958
2560
  }
2561
+ /**
2562
+ * DisTube handler
2563
+ * @type {DisTubeHandler}
2564
+ * @readonly
2565
+ */
1959
2566
  get handler() {
1960
2567
  return this.distube.handler;
1961
2568
  }
2569
+ /**
2570
+ * Check if the string is working with this plugin
2571
+ * @param {string} _string Input string
2572
+ * @returns {boolean|Promise<boolean>}
2573
+ */
1962
2574
  validate(_string) {
1963
2575
  return false;
1964
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
+ */
1965
2583
  getStreamURL(url) {
1966
2584
  return url;
1967
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
+ */
1968
2592
  getRelatedSongs(_url) {
1969
2593
  return [];
1970
2594
  }
1971
2595
  };
1972
- __name(Plugin, "Plugin");
2596
+ __name(_Plugin, "Plugin");
2597
+ var Plugin = _Plugin;
1973
2598
 
1974
2599
  // src/struct/CustomPlugin.ts
1975
- var CustomPlugin = class extends Plugin {
2600
+ var _CustomPlugin = class _CustomPlugin extends Plugin {
1976
2601
  constructor() {
1977
2602
  super(...arguments);
1978
2603
  __publicField(this, "type", "custom" /* CUSTOM */);
1979
2604
  }
1980
2605
  };
1981
- __name(CustomPlugin, "CustomPlugin");
2606
+ __name(_CustomPlugin, "CustomPlugin");
2607
+ var CustomPlugin = _CustomPlugin;
1982
2608
 
1983
2609
  // src/struct/ExtractorPlugin.ts
1984
- var ExtractorPlugin = class extends Plugin {
2610
+ var _ExtractorPlugin = class _ExtractorPlugin extends Plugin {
1985
2611
  constructor() {
1986
2612
  super(...arguments);
1987
2613
  __publicField(this, "type", "extractor" /* EXTRACTOR */);
1988
2614
  }
1989
2615
  };
1990
- __name(ExtractorPlugin, "ExtractorPlugin");
2616
+ __name(_ExtractorPlugin, "ExtractorPlugin");
2617
+ var ExtractorPlugin = _ExtractorPlugin;
1991
2618
 
1992
2619
  // src/util.ts
1993
2620
  var import_url = require("url");
@@ -1996,7 +2623,7 @@ var formatInt = /* @__PURE__ */ __name((int) => int < 10 ? `0${int}` : int, "for
1996
2623
  function formatDuration(sec) {
1997
2624
  if (!sec || !Number(sec))
1998
2625
  return "00:00";
1999
- const seconds = Math.round(sec % 60);
2626
+ const seconds = Math.floor(sec % 60);
2000
2627
  const minutes = Math.floor(sec % 3600 / 60);
2001
2628
  const hours = Math.floor(sec / 3600);
2002
2629
  if (hours > 0)
@@ -2031,12 +2658,13 @@ function parseNumber(input) {
2031
2658
  return Number(input) || 0;
2032
2659
  }
2033
2660
  __name(parseNumber, "parseNumber");
2661
+ var SUPPORTED_PROTOCOL = ["https:", "http:", "file:"];
2034
2662
  function isURL(input) {
2035
2663
  if (typeof input !== "string" || input.includes(" "))
2036
2664
  return false;
2037
2665
  try {
2038
2666
  const url = new import_url.URL(input);
2039
- if (!["https:", "http:"].includes(url.protocol) || !url.host)
2667
+ if (!SUPPORTED_PROTOCOL.some((p) => p === url.protocol))
2040
2668
  return false;
2041
2669
  } catch {
2042
2670
  return false;
@@ -2075,7 +2703,7 @@ function isMemberInstance(member) {
2075
2703
  }
2076
2704
  __name(isMemberInstance, "isMemberInstance");
2077
2705
  function isTextChannelInstance(channel) {
2078
- 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";
2079
2707
  }
2080
2708
  __name(isTextChannelInstance, "isTextChannelInstance");
2081
2709
  function isMessageInstance(message) {
@@ -2135,35 +2763,64 @@ function objectKeys(obj) {
2135
2763
  return Object.keys(obj);
2136
2764
  }
2137
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");
2138
2774
 
2139
2775
  // src/plugin/DirectLink.ts
2140
2776
  var import_undici = require("undici");
2141
- var DirectLinkPlugin = class extends ExtractorPlugin {
2777
+ var _DirectLinkPlugin = class _DirectLinkPlugin extends ExtractorPlugin {
2142
2778
  async validate(url) {
2143
2779
  try {
2144
2780
  const headers = await (0, import_undici.request)(url, { method: "HEAD" }).then((res) => res.headers);
2145
- const type = headers["content-type"];
2146
- 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)))
2147
2784
  return true;
2148
2785
  } catch {
2149
2786
  }
2150
2787
  return false;
2151
2788
  }
2789
+ // eslint-disable-next-line @typescript-eslint/require-await
2152
2790
  async resolve(url, options = {}) {
2153
2791
  url = url.replace(/\/+$/, "");
2154
- return new Song({
2155
- name: url.substring(url.lastIndexOf("/") + 1).replace(/((\?|#).*)?$/, "") || url,
2156
- url
2157
- }, options);
2792
+ return new Song(
2793
+ {
2794
+ name: url.substring(url.lastIndexOf("/") + 1).replace(/((\?|#).*)?$/, "") || url,
2795
+ url,
2796
+ src: "direct_link"
2797
+ },
2798
+ options
2799
+ );
2158
2800
  }
2159
2801
  };
2160
- __name(DirectLinkPlugin, "DirectLinkPlugin");
2802
+ __name(_DirectLinkPlugin, "DirectLinkPlugin");
2803
+ var DirectLinkPlugin = _DirectLinkPlugin;
2161
2804
 
2162
2805
  // src/DisTube.ts
2163
2806
  var import_ytsr = __toESM(require("@distube/ytsr"));
2164
2807
  var import_tiny_typed_emitter2 = require("tiny-typed-emitter");
2165
2808
  var { version } = require_package();
2166
- 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
+ */
2167
2824
  constructor(client, otp = {}) {
2168
2825
  super();
2169
2826
  __publicField(this, "handler");
@@ -2193,9 +2850,36 @@ var DisTube = class extends import_tiny_typed_emitter2.TypedEmitter {
2193
2850
  static get version() {
2194
2851
  return version;
2195
2852
  }
2853
+ /**
2854
+ * DisTube version
2855
+ * @type {string}
2856
+ */
2196
2857
  get version() {
2197
2858
  return version;
2198
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
+ */
2199
2883
  async play(voiceChannel, song, options = {}) {
2200
2884
  if (!isSupportedVoiceChannel(voiceChannel)) {
2201
2885
  throw new DisTubeError("INVALID_TYPE", "BaseGuildVoiceChannel", voiceChannel, "voiceChannel");
@@ -2262,23 +2946,39 @@ ${e.message}`;
2262
2946
  queue?._taskQueue.resolve();
2263
2947
  }
2264
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
+ */
2265
2963
  async createCustomPlaylist(songs, options = {}) {
2266
2964
  const { member, properties, parallel, metadata } = { parallel: true, ...options };
2267
2965
  if (!Array.isArray(songs))
2268
2966
  throw new DisTubeError("INVALID_TYPE", "Array", songs, "songs");
2269
2967
  if (!songs.length)
2270
2968
  throw new DisTubeError("EMPTY_ARRAY", "songs");
2271
- const filteredSongs = songs.filter((song) => song instanceof Song || isURL(song) || typeof song !== "string" && song.type === "video" /* VIDEO */);
2969
+ const filteredSongs = songs.filter(
2970
+ (song) => song instanceof Song || isURL(song) || typeof song !== "string" && song.type === "video" /* VIDEO */
2971
+ );
2272
2972
  if (!filteredSongs.length)
2273
2973
  throw new DisTubeError("NO_VALID_SONG");
2274
2974
  if (member && !isMemberInstance(member)) {
2275
2975
  throw new DisTubeError("INVALID_TYPE", "Discord.Member", member, "options.member");
2276
2976
  }
2277
- if (!filteredSongs.length)
2278
- throw new DisTubeError("NO_VALID_SONG");
2279
2977
  let resolvedSongs;
2280
2978
  if (parallel) {
2281
- const promises = filteredSongs.map((song) => this.handler.resolve(song, { member, metadata }).catch(() => void 0));
2979
+ const promises = filteredSongs.map(
2980
+ (song) => this.handler.resolve(song, { member, metadata }).catch(() => void 0)
2981
+ );
2282
2982
  resolvedSongs = (await Promise.all(promises)).filter((s) => !!s);
2283
2983
  } else {
2284
2984
  const resolved = [];
@@ -2289,6 +2989,18 @@ ${e.message}`;
2289
2989
  }
2290
2990
  return new Playlist(resolvedSongs, { member, properties, metadata });
2291
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
+ */
2292
3004
  async search(string, options = {}) {
2293
3005
  const opts = { type: "video" /* VIDEO */, limit: 10, safeSearch: false, ...options };
2294
3006
  if (typeof opts.type !== "string" || !["video", "playlist"].includes(opts.type)) {
@@ -2318,63 +3030,234 @@ ${e.message}`;
2318
3030
  return this.search(string, options);
2319
3031
  }
2320
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
+ */
2321
3051
  getQueue(guild) {
2322
3052
  return this.queues.get(guild);
2323
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
+ */
2324
3060
  pause(guild) {
2325
3061
  const q = this.getQueue(guild);
2326
3062
  if (!q)
2327
3063
  throw new DisTubeError("NO_QUEUE");
2328
3064
  return q.pause();
2329
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
+ */
2330
3072
  resume(guild) {
2331
3073
  const q = this.getQueue(guild);
2332
3074
  if (!q)
2333
3075
  throw new DisTubeError("NO_QUEUE");
2334
3076
  return q.resume();
2335
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
+ */
2336
3094
  stop(guild) {
2337
3095
  const q = this.getQueue(guild);
2338
3096
  if (!q)
2339
3097
  throw new DisTubeError("NO_QUEUE");
2340
3098
  return q.stop();
2341
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
+ */
2342
3115
  setVolume(guild, percent) {
2343
3116
  const q = this.getQueue(guild);
2344
3117
  if (!q)
2345
3118
  throw new DisTubeError("NO_QUEUE");
2346
3119
  return q.setVolume(percent);
2347
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
+ */
2348
3137
  skip(guild) {
2349
3138
  const q = this.getQueue(guild);
2350
3139
  if (!q)
2351
3140
  throw new DisTubeError("NO_QUEUE");
2352
3141
  return q.skip();
2353
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
+ */
2354
3157
  previous(guild) {
2355
3158
  const q = this.getQueue(guild);
2356
3159
  if (!q)
2357
3160
  throw new DisTubeError("NO_QUEUE");
2358
3161
  return q.previous();
2359
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
+ */
2360
3176
  shuffle(guild) {
2361
3177
  const q = this.getQueue(guild);
2362
3178
  if (!q)
2363
3179
  throw new DisTubeError("NO_QUEUE");
2364
3180
  return q.shuffle();
2365
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
+ */
2366
3200
  jump(guild, num) {
2367
3201
  const q = this.getQueue(guild);
2368
3202
  if (!q)
2369
3203
  throw new DisTubeError("NO_QUEUE");
2370
3204
  return q.jump(num);
2371
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
+ */
2372
3239
  setRepeatMode(guild, mode) {
2373
3240
  const q = this.getQueue(guild);
2374
3241
  if (!q)
2375
3242
  throw new DisTubeError("NO_QUEUE");
2376
3243
  return q.setRepeatMode(mode);
2377
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
+ */
2378
3261
  toggleAutoplay(guild) {
2379
3262
  const q = this.getQueue(guild);
2380
3263
  if (!q)
@@ -2382,29 +3265,57 @@ ${e.message}`;
2382
3265
  q.autoplay = !q.autoplay;
2383
3266
  return q.autoplay;
2384
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
+ */
2385
3273
  addRelatedSong(guild) {
2386
3274
  const q = this.getQueue(guild);
2387
3275
  if (!q)
2388
3276
  throw new DisTubeError("NO_QUEUE");
2389
3277
  return q.addRelatedSong();
2390
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
+ */
2391
3293
  seek(guild, time) {
2392
3294
  const q = this.getQueue(guild);
2393
3295
  if (!q)
2394
3296
  throw new DisTubeError("NO_QUEUE");
2395
3297
  return q.seek(time);
2396
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
+ */
2397
3305
  emitError(error, channel) {
2398
3306
  if (this.listeners("error").length) {
2399
3307
  this.emit("error", channel, error);
2400
3308
  } else {
2401
3309
  console.error(error);
2402
3310
  console.warn("Unhandled 'error' event.");
2403
- console.warn("See: https://distube.js.org/#/docs/DisTube/stable/class/DisTube?scrollTo=e-error and https://nodejs.org/api/events.html#events_error_events");
3311
+ console.warn(
3312
+ "See: https://distube.js.org/#/docs/DisTube/stable/class/DisTube?scrollTo=e-error and https://nodejs.org/api/events.html#events_error_events"
3313
+ );
2404
3314
  }
2405
3315
  }
2406
3316
  };
2407
- __name(DisTube, "DisTube");
3317
+ __name(_DisTube, "DisTube");
3318
+ var DisTube = _DisTube;
2408
3319
  // Annotate the CommonJS export names for ESM import in node:
2409
3320
  0 && (module.exports = {
2410
3321
  BaseManager,
@@ -2444,6 +3355,7 @@ __name(DisTube, "DisTube");
2444
3355
  isGuildInstance,
2445
3356
  isMemberInstance,
2446
3357
  isMessageInstance,
3358
+ isNsfwChannel,
2447
3359
  isObject,
2448
3360
  isRecord,
2449
3361
  isSnowflake,