distube 4.2.1 → 5.0.0

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
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
- var __create = Object.create;
3
2
  var __defProp = Object.defineProperty;
4
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __typeError = (msg) => {
7
+ throw TypeError(msg);
8
+ };
8
9
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
9
10
  var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
10
11
  var __commonJS = (cb, mod) => function __require() {
@@ -22,49 +23,21 @@ var __copyProps = (to, from, except, desc) => {
22
23
  }
23
24
  return to;
24
25
  };
25
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
- // If the importer is in node compatibility mode or this is not an ESM
27
- // file that has been converted to a CommonJS file using a Babel-
28
- // compatible transform (i.e. "__esModule" has not been set), then set
29
- // "default" to the CommonJS "module.exports" for node compatibility.
30
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
- mod
32
- ));
33
26
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
34
- var __publicField = (obj, key, value) => {
35
- __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
36
- return value;
37
- };
38
- var __accessCheck = (obj, member, msg) => {
39
- if (!member.has(obj))
40
- throw TypeError("Cannot " + msg);
41
- };
42
- var __privateGet = (obj, member, getter) => {
43
- __accessCheck(obj, member, "read from private field");
44
- return getter ? getter.call(obj) : member.get(obj);
45
- };
46
- var __privateAdd = (obj, member, value) => {
47
- if (member.has(obj))
48
- throw TypeError("Cannot add the same private member more than once");
49
- member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
50
- };
51
- var __privateSet = (obj, member, value, setter) => {
52
- __accessCheck(obj, member, "write to private field");
53
- setter ? setter.call(obj, value) : member.set(obj, value);
54
- return value;
55
- };
56
- var __privateMethod = (obj, member, method) => {
57
- __accessCheck(obj, member, "access private method");
58
- return method;
59
- };
27
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
28
+ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
29
+ var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
30
+ var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
31
+ var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value);
32
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
60
33
 
61
34
  // package.json
62
35
  var require_package = __commonJS({
63
36
  "package.json"(exports2, module2) {
64
37
  module2.exports = {
65
38
  name: "distube",
66
- version: "4.2.1",
67
- description: "A Discord.js module to simplify your music commands and play songs with audio filters on Discord without any API key.",
39
+ version: "5.0.0",
40
+ description: "A powerful Discord.js module for simplifying music commands and effortless playback of various sources with integrated audio filters.",
68
41
  main: "./dist/index.js",
69
42
  types: "./dist/index.d.ts",
70
43
  exports: "./dist/index.js",
@@ -83,11 +56,10 @@ var require_package = __commonJS({
83
56
  prettier: 'prettier --write "**/*.{ts,json,yml,yaml,md}"',
84
57
  build: "tsup",
85
58
  "build:check": "tsc --noEmit",
86
- update: "pnpm up -L",
87
- postinstall: "husky",
59
+ update: 'pnpm up -L "!eslint"',
60
+ prepare: "husky",
88
61
  prepublishOnly: "pnpm run lint && pnpm run test",
89
- prepack: "pnpm run build && pinst --disable",
90
- postpack: "pinst --enable",
62
+ prepack: "pnpm run build",
91
63
  "dev:add-docs-to-worktree": "git worktree add --track -b docs docs origin/docs"
92
64
  },
93
65
  repository: {
@@ -112,42 +84,38 @@ var require_package = __commonJS({
112
84
  bugs: {
113
85
  url: "https://github.com/skick1234/DisTube/issues"
114
86
  },
115
- funding: "https://github.com/skick1234/DisTube?sponsor=1",
87
+ funding: "https://github.com/skick1234/DisTube?sponsor",
116
88
  homepage: "https://distube.js.org/",
117
89
  dependencies: {
118
- "@distube/ytdl-core": "^4.13.3",
119
- "@distube/ytpl": "^1.2.1",
120
- "@distube/ytsr": "^2.0.0",
121
90
  "tiny-typed-emitter": "^2.1.0",
122
- "tough-cookie": "^4.1.3",
123
- tslib: "^2.6.2",
124
- undici: "^6.13.0"
91
+ undici: "^6.18.2"
125
92
  },
126
93
  devDependencies: {
127
- "@babel/core": "^7.24.4",
128
- "@babel/plugin-transform-class-properties": "^7.24.1",
129
- "@babel/plugin-transform-object-rest-spread": "^7.24.1",
130
- "@babel/plugin-transform-private-methods": "^7.24.1",
131
- "@babel/preset-env": "^7.24.4",
132
- "@babel/preset-typescript": "^7.24.1",
133
- "@commitlint/cli": "^19.2.2",
94
+ "@babel/core": "^7.24.6",
95
+ "@babel/plugin-transform-class-properties": "^7.24.6",
96
+ "@babel/plugin-transform-object-rest-spread": "^7.24.6",
97
+ "@babel/plugin-transform-private-methods": "^7.24.6",
98
+ "@babel/preset-env": "^7.24.6",
99
+ "@babel/preset-typescript": "^7.24.6",
100
+ "@commitlint/cli": "^19.3.0",
134
101
  "@commitlint/config-conventional": "^19.2.2",
135
- "@discordjs/voice": "^0.16.1",
102
+ "@discordjs/voice": "^0.17.0",
136
103
  "@types/jest": "^29.5.12",
137
- "@types/node": "^20.12.7",
104
+ "@types/node": "^20.14.1",
138
105
  "@types/tough-cookie": "^4.0.5",
139
- "@typescript-eslint/eslint-plugin": "^7.7.0",
140
- "@typescript-eslint/parser": "^7.7.0",
106
+ "@typescript-eslint/eslint-plugin": "^7.12.0",
107
+ "@typescript-eslint/parser": "^7.12.0",
141
108
  "babel-jest": "^29.7.0",
142
- "discord.js": "^14.14.1",
109
+ "discord.js": "^14.15.3",
143
110
  eslint: "^8.57.0",
144
111
  "eslint-config-distube": "^1.7.0",
145
112
  husky: "^9.0.11",
146
113
  jest: "^29.7.0",
147
114
  "nano-staged": "^0.8.0",
148
- pinst: "^3.0.0",
149
- prettier: "^3.2.5",
150
- tsup: "^8.0.2",
115
+ prettier: "^3.3.0",
116
+ "sodium-native": "^4.1.1",
117
+ "ts-node": "^10.9.2",
118
+ tsup: "^8.1.0",
151
119
  typedoc: "^0.25.13",
152
120
  "typedoc-material-theme": "^1.0.2",
153
121
  typescript: "^5.4.5"
@@ -176,8 +144,6 @@ var require_package = __commonJS({
176
144
  var src_exports = {};
177
145
  __export(src_exports, {
178
146
  BaseManager: () => BaseManager,
179
- CustomPlugin: () => CustomPlugin,
180
- DirectLinkPlugin: () => DirectLinkPlugin,
181
147
  DisTube: () => DisTube,
182
148
  DisTubeBase: () => DisTubeBase,
183
149
  DisTubeError: () => DisTubeError,
@@ -189,23 +155,20 @@ __export(src_exports, {
189
155
  ExtractorPlugin: () => ExtractorPlugin,
190
156
  FilterManager: () => FilterManager,
191
157
  GuildIdManager: () => GuildIdManager,
158
+ InfoExtractorPlugin: () => InfoExtractorPlugin,
192
159
  Options: () => Options,
160
+ PlayableExtractorPlugin: () => PlayableExtractorPlugin,
193
161
  Playlist: () => Playlist,
194
162
  Plugin: () => Plugin,
195
163
  PluginType: () => PluginType,
196
164
  Queue: () => Queue,
197
165
  QueueManager: () => QueueManager,
198
166
  RepeatMode: () => RepeatMode,
199
- SearchResultPlaylist: () => SearchResultPlaylist,
200
- SearchResultType: () => SearchResultType,
201
- SearchResultVideo: () => SearchResultVideo,
202
167
  Song: () => Song,
203
- StreamType: () => StreamType,
204
168
  TaskQueue: () => TaskQueue,
205
169
  checkFFmpeg: () => checkFFmpeg,
206
170
  checkIntents: () => checkIntents,
207
171
  checkInvalidKey: () => checkInvalidKey,
208
- chooseBestVideoFormat: () => chooseBestVideoFormat,
209
172
  default: () => DisTube,
210
173
  defaultFilters: () => defaultFilters,
211
174
  defaultOptions: () => defaultOptions,
@@ -216,7 +179,6 @@ __export(src_exports, {
216
179
  isMessageInstance: () => isMessageInstance,
217
180
  isNsfwChannel: () => isNsfwChannel,
218
181
  isObject: () => isObject,
219
- isRecord: () => isRecord,
220
182
  isSnowflake: () => isSnowflake,
221
183
  isSupportedVoiceChannel: () => isSupportedVoiceChannel,
222
184
  isTextChannelInstance: () => isTextChannelInstance,
@@ -224,35 +186,12 @@ __export(src_exports, {
224
186
  isURL: () => isURL,
225
187
  isVoiceChannelEmpty: () => isVoiceChannelEmpty,
226
188
  objectKeys: () => objectKeys,
227
- parseNumber: () => parseNumber,
228
189
  resolveGuildId: () => resolveGuildId,
229
- toSecond: () => toSecond,
230
190
  version: () => version
231
191
  });
232
192
  module.exports = __toCommonJS(src_exports);
233
193
 
234
194
  // src/type.ts
235
- var RepeatMode = /* @__PURE__ */ ((RepeatMode2) => {
236
- RepeatMode2[RepeatMode2["DISABLED"] = 0] = "DISABLED";
237
- RepeatMode2[RepeatMode2["SONG"] = 1] = "SONG";
238
- RepeatMode2[RepeatMode2["QUEUE"] = 2] = "QUEUE";
239
- return RepeatMode2;
240
- })(RepeatMode || {});
241
- var PluginType = /* @__PURE__ */ ((PluginType2) => {
242
- PluginType2["CUSTOM"] = "custom";
243
- PluginType2["EXTRACTOR"] = "extractor";
244
- return PluginType2;
245
- })(PluginType || {});
246
- var SearchResultType = /* @__PURE__ */ ((SearchResultType2) => {
247
- SearchResultType2["VIDEO"] = "video";
248
- SearchResultType2["PLAYLIST"] = "playlist";
249
- return SearchResultType2;
250
- })(SearchResultType || {});
251
- var StreamType = /* @__PURE__ */ ((StreamType2) => {
252
- StreamType2[StreamType2["OPUS"] = 0] = "OPUS";
253
- StreamType2[StreamType2["RAW"] = 1] = "RAW";
254
- return StreamType2;
255
- })(StreamType || {});
256
195
  var Events = /* @__PURE__ */ ((Events2) => {
257
196
  Events2["ERROR"] = "error";
258
197
  Events2["ADD_LIST"] = "addList";
@@ -265,14 +204,22 @@ var Events = /* @__PURE__ */ ((Events2) => {
265
204
  Events2["NO_RELATED"] = "noRelated";
266
205
  Events2["DISCONNECT"] = "disconnect";
267
206
  Events2["DELETE_QUEUE"] = "deleteQueue";
268
- Events2["SEARCH_CANCEL"] = "searchCancel";
269
- Events2["SEARCH_NO_RESULT"] = "searchNoResult";
270
- Events2["SEARCH_DONE"] = "searchDone";
271
- Events2["SEARCH_INVALID_ANSWER"] = "searchInvalidAnswer";
272
- Events2["SEARCH_RESULT"] = "searchResult";
273
207
  Events2["FFMPEG_DEBUG"] = "ffmpegDebug";
208
+ Events2["DEBUG"] = "debug";
274
209
  return Events2;
275
210
  })(Events || {});
211
+ var RepeatMode = /* @__PURE__ */ ((RepeatMode2) => {
212
+ RepeatMode2[RepeatMode2["DISABLED"] = 0] = "DISABLED";
213
+ RepeatMode2[RepeatMode2["SONG"] = 1] = "SONG";
214
+ RepeatMode2[RepeatMode2["QUEUE"] = 2] = "QUEUE";
215
+ return RepeatMode2;
216
+ })(RepeatMode || {});
217
+ var PluginType = /* @__PURE__ */ ((PluginType2) => {
218
+ PluginType2["EXTRACTOR"] = "extractor";
219
+ PluginType2["INFO_EXTRACTOR"] = "info-extractor";
220
+ PluginType2["PLAYABLE_EXTRACTOR"] = "playable-extractor";
221
+ return PluginType2;
222
+ })(PluginType || {});
276
223
 
277
224
  // src/constant.ts
278
225
  var defaultFilters = {
@@ -295,62 +242,59 @@ var defaultFilters = {
295
242
  var defaultOptions = {
296
243
  plugins: [],
297
244
  emitNewSongOnly: false,
298
- leaveOnEmpty: true,
299
- leaveOnFinish: false,
300
- leaveOnStop: true,
301
245
  savePreviousSongs: true,
302
- searchSongs: 0,
303
- ytdlOptions: {},
304
- searchCooldown: 60,
305
- emptyCooldown: 60,
306
246
  nsfw: false,
307
247
  emitAddSongWhenCreatingQueue: true,
308
248
  emitAddListWhenCreatingQueue: true,
309
- joinNewVoiceChannel: true,
310
- streamType: 0 /* OPUS */,
311
- directLink: true
249
+ joinNewVoiceChannel: true
312
250
  };
313
251
 
314
252
  // src/struct/DisTubeError.ts
315
253
  var import_node_util = require("util");
316
254
  var ERROR_MESSAGES = {
317
- INVALID_TYPE: (expected, got, name) => `Expected ${Array.isArray(expected) ? expected.map((e) => typeof e === "number" ? e : `'${e}'`).join(" or ") : `'${expected}'`}${name ? ` for '${name}'` : ""}, but got ${(0, import_node_util.inspect)(got)} (${typeof got})`,
318
- NUMBER_COMPARE: (name, expected, value) => `'${name}' must be ${expected} ${value}`,
319
- EMPTY_ARRAY: (name) => `'${name}' is an empty array`,
320
- EMPTY_FILTERED_ARRAY: (name, type) => `There is no valid '${type}' in the '${name}' array`,
321
- EMPTY_STRING: (name) => `'${name}' string must not be empty`,
322
- INVALID_KEY: (obj, key) => `'${key}' does not need to be provided in ${obj}`,
323
- MISSING_KEY: (obj, key) => `'${key}' needs to be provided in ${obj}`,
324
- MISSING_KEYS: (obj, key, all) => `${key.map((k) => `'${k}'`).join(all ? " and " : " or ")} need to be provided in ${obj}`,
325
- MISSING_INTENTS: (i) => `${i} intent must be provided for the Client`,
326
- DISABLED_OPTION: (o) => `DisTubeOptions.${o} is disabled`,
327
- ENABLED_OPTION: (o) => `DisTubeOptions.${o} is enabled`,
255
+ INVALID_TYPE: /* @__PURE__ */ __name((expected, got, name) => `Expected ${Array.isArray(expected) ? expected.map((e) => typeof e === "number" ? e : `'${e}'`).join(" or ") : `'${expected}'`}${name ? ` for '${name}'` : ""}, but got ${(0, import_node_util.inspect)(got)} (${typeof got})`, "INVALID_TYPE"),
256
+ NUMBER_COMPARE: /* @__PURE__ */ __name((name, expected, value) => `'${name}' must be ${expected} ${value}`, "NUMBER_COMPARE"),
257
+ EMPTY_ARRAY: /* @__PURE__ */ __name((name) => `'${name}' is an empty array`, "EMPTY_ARRAY"),
258
+ EMPTY_FILTERED_ARRAY: /* @__PURE__ */ __name((name, type) => `There is no valid '${type}' in the '${name}' array`, "EMPTY_FILTERED_ARRAY"),
259
+ EMPTY_STRING: /* @__PURE__ */ __name((name) => `'${name}' string must not be empty`, "EMPTY_STRING"),
260
+ INVALID_KEY: /* @__PURE__ */ __name((obj, key) => `'${key}' does not need to be provided in ${obj}`, "INVALID_KEY"),
261
+ MISSING_KEY: /* @__PURE__ */ __name((obj, key) => `'${key}' needs to be provided in ${obj}`, "MISSING_KEY"),
262
+ MISSING_KEYS: /* @__PURE__ */ __name((obj, key, all) => `${key.map((k) => `'${k}'`).join(all ? " and " : " or ")} need to be provided in ${obj}`, "MISSING_KEYS"),
263
+ MISSING_INTENTS: /* @__PURE__ */ __name((i) => `${i} intent must be provided for the Client`, "MISSING_INTENTS"),
264
+ DISABLED_OPTION: /* @__PURE__ */ __name((o) => `DisTubeOptions.${o} is disabled`, "DISABLED_OPTION"),
265
+ ENABLED_OPTION: /* @__PURE__ */ __name((o) => `DisTubeOptions.${o} is enabled`, "ENABLED_OPTION"),
328
266
  NOT_IN_VOICE: "User is not in any voice channel",
329
267
  VOICE_FULL: "The voice channel is full",
330
- VOICE_CONNECT_FAILED: (s) => `Cannot connect to the voice channel after ${s} seconds`,
268
+ VOICE_ALREADY_CREATED: "This guild already has a voice connection which is not managed by DisTube",
269
+ VOICE_CONNECT_FAILED: /* @__PURE__ */ __name((s) => `Cannot connect to the voice channel after ${s} seconds`, "VOICE_CONNECT_FAILED"),
331
270
  VOICE_MISSING_PERMS: "I do not have permission to join this voice channel",
332
271
  VOICE_RECONNECT_FAILED: "Cannot reconnect to the voice channel",
333
272
  VOICE_DIFFERENT_GUILD: "Cannot join a voice channel in a different guild",
334
273
  VOICE_DIFFERENT_CLIENT: "Cannot join a voice channel created by a different client",
335
- FFMPEG_EXITED: (code) => `ffmpeg exited with code ${code}`,
336
- FFMPEG_NOT_INSTALLED: (path) => `ffmpeg is not installed at '${path}' path`,
274
+ FFMPEG_EXITED: /* @__PURE__ */ __name((code) => `ffmpeg exited with code ${code}`, "FFMPEG_EXITED"),
275
+ FFMPEG_NOT_INSTALLED: /* @__PURE__ */ __name((path) => `ffmpeg is not installed at '${path}' path`, "FFMPEG_NOT_INSTALLED"),
337
276
  NO_QUEUE: "There is no playing queue in this guild",
338
277
  QUEUE_EXIST: "This guild has a Queue already",
278
+ QUEUE_STOPPED: "The queue has been stopped already",
339
279
  PAUSED: "The queue has been paused already",
340
280
  RESUMED: "The queue has been playing already",
341
281
  NO_PREVIOUS: "There is no previous song in this queue",
342
282
  NO_UP_NEXT: "There is no up next song",
343
283
  NO_SONG_POSITION: "Does not have any song at this position",
344
- NO_PLAYING: "There is no playing song in the queue",
345
- NO_RESULT: "No result found",
284
+ NO_PLAYING_SONG: "There is no playing song in the queue",
346
285
  NO_RELATED: "Cannot find any related songs",
347
286
  CANNOT_PLAY_RELATED: "Cannot play the related song",
348
287
  UNAVAILABLE_VIDEO: "This video is unavailable",
349
288
  UNPLAYABLE_FORMATS: "No playable format found",
350
289
  NON_NSFW: "Cannot play age-restricted content in non-NSFW channel",
351
290
  NOT_SUPPORTED_URL: "This url is not supported",
352
- CANNOT_RESOLVE_SONG: (t) => `Cannot resolve ${(0, import_node_util.inspect)(t)} to a Song`,
353
- NO_VALID_SONG: "'songs' array does not have any valid Song, SearchResult or url",
291
+ NOT_SUPPORTED_SONG: /* @__PURE__ */ __name((song) => `There is no plugin supporting this song (${song})`, "NOT_SUPPORTED_SONG"),
292
+ NO_VALID_SONG: "'songs' array does not have any valid Song or url",
293
+ CANNOT_RESOLVE_SONG: /* @__PURE__ */ __name((t) => `Cannot resolve ${(0, import_node_util.inspect)(t)} to a Song`, "CANNOT_RESOLVE_SONG"),
294
+ CANNOT_GET_STREAM_URL: /* @__PURE__ */ __name((song) => `Cannot get stream url from this song (${song})`, "CANNOT_GET_STREAM_URL"),
295
+ CANNOT_GET_SEARCH_QUERY: /* @__PURE__ */ __name((song) => `Cannot get search query from this song (${song})`, "CANNOT_GET_SEARCH_QUERY"),
296
+ NO_RESULT: /* @__PURE__ */ __name((query) => `Cannot get song stream from this query (${query})`, "NO_RESULT"),
297
+ NO_STREAM_URL: /* @__PURE__ */ __name((song) => `No stream url attached (${song})`, "NO_STREAM_URL"),
354
298
  EMPTY_FILTERED_PLAYLIST: "There is no valid video in the playlist\nMaybe age-restricted contents is filtered because you are in non-NSFW channel",
355
299
  EMPTY_PLAYLIST: "There is no valid video in the playlist"
356
300
  };
@@ -362,8 +306,7 @@ var _DisTubeError = class _DisTubeError extends Error {
362
306
  super(getErrorMessage(code, ...args));
363
307
  __publicField(this, "errorCode");
364
308
  this.errorCode = code;
365
- if (Error.captureStackTrace)
366
- Error.captureStackTrace(this, _DisTubeError);
309
+ if (Error.captureStackTrace) Error.captureStackTrace(this, _DisTubeError);
367
310
  }
368
311
  get name() {
369
312
  return `DisTubeError [${this.errorCode}]`;
@@ -377,11 +320,9 @@ var DisTubeError = _DisTubeError;
377
320
 
378
321
  // src/struct/TaskQueue.ts
379
322
  var _Task = class _Task {
380
- constructor(resolveInfo) {
323
+ constructor() {
381
324
  __publicField(this, "resolve");
382
325
  __publicField(this, "promise");
383
- __publicField(this, "resolveInfo");
384
- this.resolveInfo = resolveInfo;
385
326
  this.promise = new Promise((res) => {
386
327
  this.resolve = res;
387
328
  });
@@ -399,12 +340,10 @@ var _TaskQueue = class _TaskQueue {
399
340
  }
400
341
  /**
401
342
  * Waits for last task finished and queues a new task
402
- *
403
- * @param resolveInfo - Whether the task is a resolving info task
404
343
  */
405
- queuing(resolveInfo = false) {
344
+ queuing() {
406
345
  const next = this.remaining ? __privateGet(this, _tasks)[__privateGet(this, _tasks).length - 1].promise : Promise.resolve();
407
- __privateGet(this, _tasks).push(new Task(resolveInfo));
346
+ __privateGet(this, _tasks).push(new Task());
408
347
  return next;
409
348
  }
410
349
  /**
@@ -419,12 +358,6 @@ var _TaskQueue = class _TaskQueue {
419
358
  get remaining() {
420
359
  return __privateGet(this, _tasks).length;
421
360
  }
422
- /**
423
- * Whether or not having a resolving info task
424
- */
425
- get hasResolveTask() {
426
- return __privateGet(this, _tasks).some((t) => t.resolveInfo);
427
- }
428
361
  };
429
362
  _tasks = new WeakMap();
430
363
  __name(_TaskQueue, "TaskQueue");
@@ -434,49 +367,46 @@ var TaskQueue = _TaskQueue;
434
367
  var _metadata, _member;
435
368
  var _Playlist = class _Playlist {
436
369
  /**
437
- * Create a playlist
438
- *
439
- * @param playlist - Playlist
440
- * @param options - Optional options
370
+ * Create a Playlist
371
+ * @param playlist - Raw playlist info
372
+ * @param options - Optional data
441
373
  */
442
- constructor(playlist, options = {}) {
374
+ constructor(playlist, { member, metadata } = {}) {
375
+ /**
376
+ * Playlist source.
377
+ */
443
378
  __publicField(this, "source");
379
+ /**
380
+ * Songs in the playlist.
381
+ */
444
382
  __publicField(this, "songs");
383
+ /**
384
+ * Playlist ID.
385
+ */
386
+ __publicField(this, "id");
387
+ /**
388
+ * Playlist name.
389
+ */
445
390
  __publicField(this, "name");
446
- __privateAdd(this, _metadata, void 0);
447
- __privateAdd(this, _member, void 0);
391
+ /**
392
+ * Playlist URL.
393
+ */
448
394
  __publicField(this, "url");
395
+ /**
396
+ * Playlist thumbnail.
397
+ */
449
398
  __publicField(this, "thumbnail");
450
- const { member, properties, metadata } = options;
451
- if (typeof playlist !== "object" || !Array.isArray(playlist) && ["source", "songs"].some((key) => !(key in playlist))) {
452
- throw new DisTubeError("INVALID_TYPE", ["Array<Song>", "PlaylistInfo"], playlist, "playlist");
453
- }
454
- if (typeof properties !== "undefined" && !isRecord(properties)) {
455
- throw new DisTubeError("INVALID_TYPE", "object", properties, "properties");
456
- }
457
- if (Array.isArray(playlist)) {
458
- this.source = "youtube";
459
- if (!playlist.length)
460
- throw new DisTubeError("EMPTY_PLAYLIST");
461
- this.songs = playlist;
462
- this.name = this.songs[0].name ? `${this.songs[0].name} and ${this.songs.length - 1} more songs.` : `${this.songs.length} songs playlist`;
463
- this.thumbnail = this.songs[0].thumbnail;
464
- this.member = member;
465
- } else {
466
- this.source = playlist.source.toLowerCase();
467
- if (!Array.isArray(playlist.songs) || !playlist.songs.length)
468
- throw new DisTubeError("EMPTY_PLAYLIST");
469
- this.songs = playlist.songs;
470
- this.name = playlist.name || // eslint-disable-next-line deprecation/deprecation
471
- playlist.title || (this.songs[0].name ? `${this.songs[0].name} and ${this.songs.length - 1} more songs.` : `${this.songs.length} songs playlist`);
472
- this.url = playlist.url || playlist.webpage_url;
473
- this.thumbnail = playlist.thumbnail || this.songs[0].thumbnail;
474
- this.member = member || playlist.member;
475
- }
476
- this.songs.forEach((s) => s.constructor.name === "Song" && (s.playlist = this));
477
- if (properties)
478
- for (const [key, value] of Object.entries(properties))
479
- this[key] = value;
399
+ __privateAdd(this, _metadata);
400
+ __privateAdd(this, _member);
401
+ if (!Array.isArray(playlist.songs) || !playlist.songs.length) throw new DisTubeError("EMPTY_PLAYLIST");
402
+ this.source = playlist.source.toLowerCase();
403
+ this.songs = playlist.songs;
404
+ this.name = playlist.name;
405
+ this.id = playlist.id;
406
+ this.url = playlist.url;
407
+ this.thumbnail = playlist.thumbnail;
408
+ this.member = member;
409
+ this.songs.forEach((s) => s.playlist = this);
480
410
  this.metadata = metadata;
481
411
  }
482
412
  /**
@@ -498,10 +428,9 @@ var _Playlist = class _Playlist {
498
428
  return __privateGet(this, _member);
499
429
  }
500
430
  set member(member) {
501
- if (!isMemberInstance(member))
502
- return;
431
+ if (!isMemberInstance(member)) return;
503
432
  __privateSet(this, _member, member);
504
- this.songs.forEach((s) => s.constructor.name === "Song" && (s.member = this.member));
433
+ this.songs.forEach((s) => s.member = this.member);
505
434
  }
506
435
  /**
507
436
  * User requested.
@@ -509,12 +438,15 @@ var _Playlist = class _Playlist {
509
438
  get user() {
510
439
  return this.member?.user;
511
440
  }
441
+ /**
442
+ * Optional metadata that can be used to identify the playlist.
443
+ */
512
444
  get metadata() {
513
445
  return __privateGet(this, _metadata);
514
446
  }
515
447
  set metadata(metadata) {
516
448
  __privateSet(this, _metadata, metadata);
517
- this.songs.forEach((s) => s.constructor.name === "Song" && (s.metadata = metadata));
449
+ this.songs.forEach((s) => s.metadata = metadata);
518
450
  }
519
451
  };
520
452
  _metadata = new WeakMap();
@@ -522,225 +454,144 @@ _member = new WeakMap();
522
454
  __name(_Playlist, "Playlist");
523
455
  var Playlist = _Playlist;
524
456
 
525
- // src/struct/SearchResult.ts
526
- var _ISearchResult = class _ISearchResult {
527
- /**
528
- * Create a search result
529
- *
530
- * @param info - ytsr result
531
- */
532
- constructor(info) {
533
- __publicField(this, "source");
534
- __publicField(this, "id");
535
- __publicField(this, "name");
536
- __publicField(this, "url");
537
- __publicField(this, "uploader");
538
- this.source = "youtube";
539
- this.id = info.id;
540
- this.name = info.name;
541
- this.url = info.url;
542
- this.uploader = {
543
- name: void 0,
544
- url: void 0
545
- };
546
- }
547
- };
548
- __name(_ISearchResult, "ISearchResult");
549
- var ISearchResult = _ISearchResult;
550
- var _SearchResultVideo = class _SearchResultVideo extends ISearchResult {
551
- constructor(info) {
552
- super(info);
553
- __publicField(this, "type");
554
- __publicField(this, "views");
555
- __publicField(this, "isLive");
556
- __publicField(this, "duration");
557
- __publicField(this, "formattedDuration");
558
- __publicField(this, "thumbnail");
559
- if (info.type !== "video")
560
- throw new DisTubeError("INVALID_TYPE", "video", info.type, "type");
561
- this.type = "video" /* VIDEO */;
562
- this.views = info.views;
563
- this.isLive = info.isLive;
564
- this.duration = this.isLive ? 0 : toSecond(info.duration);
565
- this.formattedDuration = this.isLive ? "Live" : formatDuration(this.duration);
566
- this.thumbnail = info.thumbnail;
567
- this.uploader = {
568
- name: info.author?.name,
569
- url: info.author?.url
570
- };
571
- }
572
- };
573
- __name(_SearchResultVideo, "SearchResultVideo");
574
- var SearchResultVideo = _SearchResultVideo;
575
- var _SearchResultPlaylist = class _SearchResultPlaylist extends ISearchResult {
576
- constructor(info) {
577
- super(info);
578
- __publicField(this, "type");
579
- __publicField(this, "length");
580
- if (info.type !== "playlist")
581
- throw new DisTubeError("INVALID_TYPE", "playlist", info.type, "type");
582
- this.type = "playlist" /* PLAYLIST */;
583
- this.length = info.length;
584
- this.uploader = {
585
- name: info.owner?.name,
586
- url: info.owner?.url
587
- };
588
- }
589
- };
590
- __name(_SearchResultPlaylist, "SearchResultPlaylist");
591
- var SearchResultPlaylist = _SearchResultPlaylist;
592
-
593
457
  // src/struct/Song.ts
594
458
  var _metadata2, _member2, _playlist;
595
459
  var _Song = class _Song {
596
460
  /**
597
461
  * Create a Song
598
462
  *
599
- * @param info - Raw info
600
- * @param options - Optional options
463
+ * @param info - Raw song info
464
+ * @param options - Optional data
601
465
  */
602
- constructor(info, options = {}) {
466
+ constructor(info, { member, metadata } = {}) {
467
+ /**
468
+ * The source of this song info
469
+ */
603
470
  __publicField(this, "source");
604
- __privateAdd(this, _metadata2, void 0);
605
- __publicField(this, "formats");
606
- __privateAdd(this, _member2, void 0);
471
+ /**
472
+ * Song ID.
473
+ */
607
474
  __publicField(this, "id");
475
+ /**
476
+ * Song name.
477
+ */
608
478
  __publicField(this, "name");
479
+ /**
480
+ * Indicates if the song is an active live.
481
+ */
609
482
  __publicField(this, "isLive");
483
+ /**
484
+ * Song duration.
485
+ */
610
486
  __publicField(this, "duration");
487
+ /**
488
+ * Formatted duration string (`hh:mm:ss`, `mm:ss` or `Live`).
489
+ */
611
490
  __publicField(this, "formattedDuration");
491
+ /**
492
+ * Song URL.
493
+ */
612
494
  __publicField(this, "url");
613
- __publicField(this, "streamURL");
495
+ /**
496
+ * Song thumbnail.
497
+ */
614
498
  __publicField(this, "thumbnail");
615
- __publicField(this, "related");
499
+ /**
500
+ * Song view count
501
+ */
616
502
  __publicField(this, "views");
503
+ /**
504
+ * Song like count
505
+ */
617
506
  __publicField(this, "likes");
507
+ /**
508
+ * Song dislike count
509
+ */
618
510
  __publicField(this, "dislikes");
619
- __publicField(this, "uploader");
620
- __publicField(this, "age_restricted");
621
- __publicField(this, "chapters");
511
+ /**
512
+ * Song repost (share) count
513
+ */
622
514
  __publicField(this, "reposts");
623
- __privateAdd(this, _playlist, void 0);
624
- const { member, source, metadata } = { source: "youtube", ...options };
625
- if (typeof source !== "string" || info.src && typeof info.src !== "string") {
626
- throw new DisTubeError("INVALID_TYPE", "string", source, "source");
627
- }
628
- this.source = (info?.src || source).toLowerCase();
515
+ /**
516
+ * Song uploader
517
+ */
518
+ __publicField(this, "uploader");
519
+ /**
520
+ * Whether or not an age-restricted content
521
+ */
522
+ __publicField(this, "ageRestricted");
523
+ /**
524
+ * Stream info
525
+ */
526
+ __publicField(this, "stream");
527
+ /**
528
+ * The plugin that created this song
529
+ */
530
+ __publicField(this, "plugin");
531
+ __privateAdd(this, _metadata2);
532
+ __privateAdd(this, _member2);
533
+ __privateAdd(this, _playlist);
534
+ this.source = info.source.toLowerCase();
629
535
  this.metadata = metadata;
630
536
  this.member = member;
631
- if (this.source === "youtube") {
632
- this._patchYouTube(info);
633
- } else {
634
- this._patchOther(info);
635
- }
636
- }
637
- _patchYouTube(i) {
638
- const info = i;
639
- if (info.full === true) {
640
- this.formats = info.formats;
641
- const err = require("@distube/ytdl-core/lib/utils").playError(info.player_response, [
642
- "UNPLAYABLE",
643
- "LIVE_STREAM_OFFLINE",
644
- "LOGIN_REQUIRED"
645
- ]);
646
- if (err)
647
- throw err;
648
- if (!info.formats?.length)
649
- throw new DisTubeError("UNAVAILABLE_VIDEO");
650
- }
651
- const details = info.videoDetails || info;
652
- this.id = details.videoId || details.id;
653
- this.name = details.title || details.name;
654
- this.isLive = Boolean(details.isLive);
655
- this.duration = this.isLive ? 0 : toSecond(details.lengthSeconds || details.length_seconds || details.duration);
656
- this.formattedDuration = this.isLive ? "Live" : formatDuration(this.duration);
657
- this.url = `https://www.youtube.com/watch?v=${this.id}`;
658
- this.streamURL = void 0;
659
- this.thumbnail = details.thumbnails?.sort((a, b) => b.width - a.width)?.[0]?.url || details.thumbnail?.url || details.thumbnail;
660
- this.related = info?.related_videos || details.related || [];
661
- if (!Array.isArray(this.related))
662
- throw new DisTubeError("INVALID_TYPE", "Array", this.related, "Song#related");
663
- this.related = this.related.map((v) => new _Song(v, { source: this.source, metadata: this.metadata }));
664
- this.views = parseNumber(details.viewCount || details.view_count || details.views);
665
- this.likes = parseNumber(details.likes);
666
- this.dislikes = parseNumber(details.dislikes);
667
- this.uploader = {
668
- name: info.uploader?.name || details.author?.name,
669
- url: info.uploader?.url || details.author?.channel_url || details.author?.url
670
- };
671
- this.age_restricted = Boolean(details.age_restricted);
672
- this.chapters = details.chapters || [];
673
- this.reposts = 0;
674
- }
675
- /**
676
- * Patch data from other source
677
- *
678
- * @param info - Video info
679
- */
680
- _patchOther(info) {
681
537
  this.id = info.id;
682
- this.name = info.title || info.name;
683
- this.isLive = Boolean(info.is_live || info.isLive);
684
- this.duration = this.isLive ? 0 : toSecond(info._duration_raw || info.duration);
538
+ this.name = info.name;
539
+ this.isLive = info.isLive;
540
+ this.duration = this.isLive || !info.duration ? 0 : info.duration;
685
541
  this.formattedDuration = this.isLive ? "Live" : formatDuration(this.duration);
686
- this.url = info.webpage_url || info.url;
542
+ this.url = info.url;
687
543
  this.thumbnail = info.thumbnail;
688
- this.related = info.related || [];
689
- if (!Array.isArray(this.related))
690
- throw new DisTubeError("INVALID_TYPE", "Array", this.related, "Song#related");
691
- this.related = this.related.map((i) => new _Song(i, { source: this.source, metadata: this.metadata }));
692
- this.views = parseNumber(info.view_count || info.views);
693
- this.likes = parseNumber(info.like_count || info.likes);
694
- this.dislikes = parseNumber(info.dislike_count || info.dislikes);
695
- this.reposts = parseNumber(info.repost_count || info.reposts);
696
- if (typeof info.uploader === "string") {
697
- this.uploader = {
698
- name: info.uploader,
699
- url: info.uploader_url
700
- };
701
- } else {
702
- this.uploader = {
703
- name: info.uploader?.name,
704
- url: info.uploader?.url
705
- };
706
- }
707
- this.age_restricted = info.age_restricted || Boolean(info.age_limit) && parseNumber(info.age_limit) >= 18;
708
- this.chapters = info.chapters || [];
544
+ this.views = info.views;
545
+ this.likes = info.likes;
546
+ this.dislikes = info.dislikes;
547
+ this.reposts = info.reposts;
548
+ this.uploader = {
549
+ name: info.uploader?.name,
550
+ url: info.uploader?.url
551
+ };
552
+ this.ageRestricted = info.ageRestricted;
553
+ this.stream = { playFromSource: info.playFromSource };
554
+ this.plugin = info.plugin;
709
555
  }
710
556
  /**
711
- * The playlist added this song
557
+ * The playlist this song belongs to
712
558
  */
713
559
  get playlist() {
714
560
  return __privateGet(this, _playlist);
715
561
  }
716
562
  set playlist(playlist) {
717
- if (!(playlist instanceof Playlist))
718
- throw new DisTubeError("INVALID_TYPE", "Playlist", playlist, "Song#playlist");
563
+ if (!(playlist instanceof Playlist)) throw new DisTubeError("INVALID_TYPE", "Playlist", playlist, "Song#playlist");
719
564
  __privateSet(this, _playlist, playlist);
720
565
  this.member = playlist.member;
721
566
  }
722
567
  /**
723
- * User requested.
568
+ * User requested to play this song.
724
569
  */
725
570
  get member() {
726
571
  return __privateGet(this, _member2);
727
572
  }
728
573
  set member(member) {
729
- if (isMemberInstance(member))
730
- __privateSet(this, _member2, member);
574
+ if (isMemberInstance(member)) __privateSet(this, _member2, member);
731
575
  }
732
576
  /**
733
- * User requested.
577
+ * User requested to play this song.
734
578
  */
735
579
  get user() {
736
580
  return this.member?.user;
737
581
  }
582
+ /**
583
+ * Optional metadata that can be used to identify the song. This is attached by the
584
+ * {@link DisTube#play} method.
585
+ */
738
586
  get metadata() {
739
587
  return __privateGet(this, _metadata2);
740
588
  }
741
589
  set metadata(metadata) {
742
590
  __privateSet(this, _metadata2, metadata);
743
591
  }
592
+ toString() {
593
+ return this.name || this.url || this.id || "Unknown";
594
+ }
744
595
  };
745
596
  _metadata2 = new WeakMap();
746
597
  _member2 = new WeakMap();
@@ -756,7 +607,6 @@ var _DisTubeBase = class _DisTubeBase {
756
607
  }
757
608
  /**
758
609
  * Emit the {@link DisTube} of this base
759
- *
760
610
  * @param eventName - Event name
761
611
  * @param args - arguments
762
612
  */
@@ -765,12 +615,19 @@ var _DisTubeBase = class _DisTubeBase {
765
615
  }
766
616
  /**
767
617
  * Emit error event
768
- *
769
618
  * @param error - error
770
- * @param channel - Text channel where the error is encountered.
619
+ * @param queue - The queue encountered the error
620
+ * @param song - The playing song when encountered the error
771
621
  */
772
- emitError(error, channel) {
773
- this.distube.emitError(error, channel);
622
+ emitError(error, queue, song) {
623
+ this.distube.emitError(error, queue, song);
624
+ }
625
+ /**
626
+ * Emit debug event
627
+ * @param message - debug message
628
+ */
629
+ debug(message) {
630
+ this.distube.debug(message);
774
631
  }
775
632
  /**
776
633
  * The queue manager
@@ -802,6 +659,12 @@ var _DisTubeBase = class _DisTubeBase {
802
659
  get handler() {
803
660
  return this.distube.handler;
804
661
  }
662
+ /**
663
+ * DisTube plugins
664
+ */
665
+ get plugins() {
666
+ return this.distube.plugins;
667
+ }
805
668
  };
806
669
  __name(_DisTubeBase, "DisTubeBase");
807
670
  var DisTubeBase = _DisTubeBase;
@@ -810,34 +673,28 @@ var DisTubeBase = _DisTubeBase;
810
673
  var import_discord = require("discord.js");
811
674
  var import_tiny_typed_emitter = require("tiny-typed-emitter");
812
675
  var import_voice = require("@discordjs/voice");
813
- var _channel, _volume, _br, br_fn, _join, join_fn;
676
+ var _channel, _volume, _DisTubeVoice_instances, join_fn;
814
677
  var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedEmitter {
815
678
  constructor(voiceManager, channel) {
816
679
  super();
817
- __privateAdd(this, _br);
818
- __privateAdd(this, _join);
680
+ __privateAdd(this, _DisTubeVoice_instances);
819
681
  __publicField(this, "id");
820
682
  __publicField(this, "voices");
821
683
  __publicField(this, "audioPlayer");
822
684
  __publicField(this, "connection");
823
- __publicField(this, "audioResource");
824
685
  __publicField(this, "emittedError");
825
686
  __publicField(this, "isDisconnected", false);
826
687
  __publicField(this, "stream");
827
- __privateAdd(this, _channel, void 0);
688
+ __privateAdd(this, _channel);
828
689
  __privateAdd(this, _volume, 100);
829
690
  this.voices = voiceManager;
830
691
  this.id = channel.guildId;
831
692
  this.channel = channel;
832
693
  this.voices.add(this.id, this);
833
694
  this.audioPlayer = (0, import_voice.createAudioPlayer)().on(import_voice.AudioPlayerStatus.Idle, (oldState) => {
834
- if (oldState.status !== import_voice.AudioPlayerStatus.Idle) {
835
- delete this.audioResource;
836
- this.emit("finish");
837
- }
838
- }).on(import_voice.AudioPlayerStatus.Playing, () => __privateMethod(this, _br, br_fn).call(this)).on("error", (error) => {
839
- if (this.emittedError)
840
- return;
695
+ if (oldState.status !== import_voice.AudioPlayerStatus.Idle) this.emit("finish");
696
+ }).on("error", (error) => {
697
+ if (this.emittedError) return;
841
698
  this.emittedError = true;
842
699
  this.emit("error", error);
843
700
  });
@@ -872,13 +729,10 @@ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedE
872
729
  return this.connection?.joinConfig?.channelId ?? void 0;
873
730
  }
874
731
  get channel() {
875
- if (!this.channelId)
876
- return __privateGet(this, _channel);
877
- if (__privateGet(this, _channel)?.id === this.channelId)
878
- return __privateGet(this, _channel);
732
+ if (!this.channelId) return __privateGet(this, _channel);
733
+ if (__privateGet(this, _channel)?.id === this.channelId) return __privateGet(this, _channel);
879
734
  const channel = this.voices.client.channels.cache.get(this.channelId);
880
- if (!channel)
881
- return __privateGet(this, _channel);
735
+ if (!channel) return __privateGet(this, _channel);
882
736
  for (const type of import_discord.Constants.VoiceBasedChannelTypes) {
883
737
  if (channel.type === type) {
884
738
  __privateSet(this, _channel, channel);
@@ -891,38 +745,28 @@ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedE
891
745
  if (!isSupportedVoiceChannel(channel)) {
892
746
  throw new DisTubeError("INVALID_TYPE", "BaseGuildVoiceChannel", channel, "DisTubeVoice#channel");
893
747
  }
894
- if (channel.guildId !== this.id)
895
- throw new DisTubeError("VOICE_DIFFERENT_GUILD");
896
- if (channel.client.user?.id !== this.voices.client.user?.id)
897
- throw new DisTubeError("VOICE_DIFFERENT_CLIENT");
898
- if (channel.id === this.channelId)
899
- return;
748
+ if (channel.guildId !== this.id) throw new DisTubeError("VOICE_DIFFERENT_GUILD");
749
+ if (channel.client.user?.id !== this.voices.client.user?.id) throw new DisTubeError("VOICE_DIFFERENT_CLIENT");
750
+ if (channel.id === this.channelId) return;
900
751
  if (!channel.joinable) {
901
- if (channel.full)
902
- throw new DisTubeError("VOICE_FULL");
903
- else
904
- throw new DisTubeError("VOICE_MISSING_PERMS");
752
+ if (channel.full) throw new DisTubeError("VOICE_FULL");
753
+ else throw new DisTubeError("VOICE_MISSING_PERMS");
905
754
  }
906
- this.connection = __privateMethod(this, _join, join_fn).call(this, channel);
755
+ this.connection = __privateMethod(this, _DisTubeVoice_instances, join_fn).call(this, channel);
907
756
  __privateSet(this, _channel, channel);
908
- __privateMethod(this, _br, br_fn).call(this);
909
757
  }
910
758
  /**
911
759
  * Join a voice channel with this connection
912
- *
913
760
  * @param channel - A voice channel
914
761
  */
915
762
  async join(channel) {
916
763
  const TIMEOUT = 3e4;
917
- if (channel)
918
- this.channel = channel;
764
+ if (channel) this.channel = channel;
919
765
  try {
920
766
  await (0, import_voice.entersState)(this.connection, import_voice.VoiceConnectionStatus.Ready, TIMEOUT);
921
767
  } catch {
922
- if (this.connection.state.status === import_voice.VoiceConnectionStatus.Ready)
923
- return this;
924
- if (this.connection.state.status !== import_voice.VoiceConnectionStatus.Destroyed)
925
- this.connection.destroy();
768
+ if (this.connection.state.status === import_voice.VoiceConnectionStatus.Ready) return this;
769
+ if (this.connection.state.status !== import_voice.VoiceConnectionStatus.Destroyed) this.connection.destroy();
926
770
  this.voices.remove(this.id);
927
771
  throw new DisTubeError("VOICE_CONNECT_FAILED", TIMEOUT / 1e3);
928
772
  }
@@ -930,7 +774,6 @@ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedE
930
774
  }
931
775
  /**
932
776
  * Leave the voice channel of this connection
933
- *
934
777
  * @param error - Optional, an error to emit with 'error' event.
935
778
  */
936
779
  leave(error) {
@@ -939,42 +782,33 @@ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedE
939
782
  this.emit("disconnect", error);
940
783
  this.isDisconnected = true;
941
784
  }
942
- if (this.connection.state.status !== import_voice.VoiceConnectionStatus.Destroyed)
943
- this.connection.destroy();
785
+ if (this.connection.state.status !== import_voice.VoiceConnectionStatus.Destroyed) this.connection.destroy();
944
786
  this.voices.remove(this.id);
945
787
  }
946
788
  /**
947
789
  * Stop the playing stream
948
- *
949
790
  * @param force - If true, will force the {@link DisTubeVoice#audioPlayer} to enter the Idle state even
950
- * if the {@link DisTubeVoice#audioResource} has silence padding frames.
791
+ * if the {@link DisTubeStream#audioResource} has silence padding frames.
951
792
  */
952
793
  stop(force = false) {
953
794
  this.audioPlayer.stop(force);
954
- this.stream?.kill?.();
955
795
  }
956
796
  /**
957
797
  * Play a {@link DisTubeStream}
958
- *
959
798
  * @param dtStream - DisTubeStream
960
799
  */
961
800
  play(dtStream) {
962
801
  this.emittedError = false;
963
802
  dtStream.on("error", (error) => {
964
- if (this.emittedError || error.code === "ERR_STREAM_PREMATURE_CLOSE")
965
- return;
803
+ if (this.emittedError || error.code === "ERR_STREAM_PREMATURE_CLOSE") return;
966
804
  this.emittedError = true;
967
805
  this.emit("error", error);
968
806
  });
969
- this.audioResource = (0, import_voice.createAudioResource)(dtStream.stream, {
970
- inputType: dtStream.type,
971
- inlineVolume: true
972
- });
973
- this.volume = __privateGet(this, _volume);
974
- if (this.audioPlayer.state.status !== import_voice.AudioPlayerStatus.Paused)
975
- this.audioPlayer.play(this.audioResource);
976
- this.stream?.kill?.();
807
+ if (this.audioPlayer.state.status !== import_voice.AudioPlayerStatus.Paused) this.audioPlayer.play(dtStream.audioResource);
808
+ this.stream?.kill();
977
809
  this.stream = dtStream;
810
+ this.volume = __privateGet(this, _volume);
811
+ dtStream.spawn();
978
812
  }
979
813
  set volume(volume) {
980
814
  if (typeof volume !== "number" || isNaN(volume)) {
@@ -984,8 +818,11 @@ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedE
984
818
  throw new DisTubeError("NUMBER_COMPARE", "Volume", "bigger or equal to", 0);
985
819
  }
986
820
  __privateSet(this, _volume, volume);
987
- this.audioResource?.volume?.setVolume(Math.pow(__privateGet(this, _volume) / 100, 0.5 / Math.log10(2)));
821
+ this.stream?.setVolume(Math.pow(__privateGet(this, _volume) / 100, 0.5 / Math.log10(2)));
988
822
  }
823
+ /**
824
+ * Get or set the volume percentage
825
+ */
989
826
  get volume() {
990
827
  return __privateGet(this, _volume);
991
828
  }
@@ -993,19 +830,19 @@ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedE
993
830
  * Playback duration of the audio resource in seconds
994
831
  */
995
832
  get playbackDuration() {
996
- return (this.audioResource?.playbackDuration ?? 0) / 1e3;
833
+ return (this.stream?.audioResource?.playbackDuration ?? 0) / 1e3;
997
834
  }
998
835
  pause() {
999
836
  this.audioPlayer.pause();
1000
837
  }
1001
838
  unpause() {
1002
839
  const state = this.audioPlayer.state;
1003
- if (state.status !== import_voice.AudioPlayerStatus.Paused)
1004
- return;
1005
- if (this.audioResource && state.resource !== this.audioResource)
1006
- this.audioPlayer.play(this.audioResource);
1007
- else
840
+ if (state.status !== import_voice.AudioPlayerStatus.Paused) return;
841
+ if (this.stream?.audioResource && state.resource !== this.stream.audioResource) {
842
+ this.audioPlayer.play(this.stream.audioResource);
843
+ } else {
1008
844
  this.audioPlayer.unpause();
845
+ }
1009
846
  }
1010
847
  /**
1011
848
  * Whether the bot is self-deafened
@@ -1021,9 +858,7 @@ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedE
1021
858
  }
1022
859
  /**
1023
860
  * Self-deafens/undeafens the bot.
1024
- *
1025
861
  * @param selfDeaf - Whether or not the bot should be self-deafened
1026
- *
1027
862
  * @returns true if the voice state was successfully updated, otherwise false
1028
863
  */
1029
864
  setSelfDeaf(selfDeaf) {
@@ -1037,9 +872,7 @@ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedE
1037
872
  }
1038
873
  /**
1039
874
  * Self-mutes/unmutes the bot.
1040
- *
1041
875
  * @param selfMute - Whether or not the bot should be self-muted
1042
- *
1043
876
  * @returns true if the voice state was successfully updated, otherwise false
1044
877
  */
1045
878
  setSelfMute(selfMute) {
@@ -1060,12 +893,7 @@ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedE
1060
893
  };
1061
894
  _channel = new WeakMap();
1062
895
  _volume = new WeakMap();
1063
- _br = new WeakSet();
1064
- br_fn = /* @__PURE__ */ __name(function() {
1065
- if (this.audioResource?.encoder?.encoder)
1066
- this.audioResource.encoder.setBitrate(this.channel.bitrate);
1067
- }, "#br");
1068
- _join = new WeakSet();
896
+ _DisTubeVoice_instances = new WeakSet();
1069
897
  join_fn = /* @__PURE__ */ __name(function(channel) {
1070
898
  return (0, import_voice.joinVoiceChannel)({
1071
899
  channelId: channel.id,
@@ -1078,57 +906,51 @@ __name(_DisTubeVoice, "DisTubeVoice");
1078
906
  var DisTubeVoice = _DisTubeVoice;
1079
907
 
1080
908
  // src/core/DisTubeStream.ts
1081
- var import_node_stream = require("stream");
909
+ var import_stream = require("stream");
1082
910
  var import_child_process = require("child_process");
1083
911
  var import_tiny_typed_emitter2 = require("tiny-typed-emitter");
1084
912
  var import_voice2 = require("@discordjs/voice");
1085
- var chooseBestVideoFormat = /* @__PURE__ */ __name(({ duration, formats, isLive }) => formats && formats.filter((f) => f.hasAudio && (duration < 10 * 60 || f.hasVideo) && (!isLive || f.isHLS)).sort((a, b) => Number(b.audioBitrate) - Number(a.audioBitrate) || Number(a.bitrate) - Number(b.bitrate))[0], "chooseBestVideoFormat");
1086
913
  var checked = process.env.NODE_ENV === "test";
1087
914
  var checkFFmpeg = /* @__PURE__ */ __name((distube) => {
1088
- if (checked)
1089
- return;
915
+ if (checked) return;
1090
916
  const path = distube.options.ffmpeg.path;
1091
917
  const debug = /* @__PURE__ */ __name((str) => distube.emit("ffmpegDebug" /* FFMPEG_DEBUG */, str), "debug");
1092
918
  try {
1093
919
  debug(`[test] spawn ffmpeg at '${path}' path`);
1094
920
  const process2 = (0, import_child_process.spawnSync)(path, ["-h"], { windowsHide: true, shell: true, encoding: "utf-8" });
1095
- if (process2.error)
1096
- throw process2.error;
1097
- if (process2.stderr && !process2.stdout)
1098
- throw new Error(process2.stderr);
921
+ if (process2.error) throw process2.error;
922
+ if (process2.stderr && !process2.stdout) throw new Error(process2.stderr);
1099
923
  const result = process2.output.join("\n");
1100
924
  const version2 = /ffmpeg version (\S+)/iu.exec(result)?.[1];
1101
- if (!version2)
1102
- throw new Error("Invalid FFmpeg version");
925
+ if (!version2) throw new Error("Invalid FFmpeg version");
1103
926
  debug(`[test] ffmpeg version: ${version2}`);
1104
- if (result.includes("--enable-libopus")) {
1105
- debug("[test] ffmpeg supports libopus");
1106
- } else {
1107
- debug("[test] ffmpeg does not support libopus");
1108
- distube.options.streamType = 1 /* RAW */;
1109
- }
1110
927
  } catch (e) {
1111
928
  debug(`[test] failed to spawn ffmpeg at '${path}': ${e?.stack ?? e}`);
1112
929
  throw new DisTubeError("FFMPEG_NOT_INSTALLED", path);
1113
930
  }
1114
931
  checked = true;
1115
932
  }, "checkFFmpeg");
933
+ var _ffmpegPath, _opts;
1116
934
  var _DisTubeStream = class _DisTubeStream extends import_tiny_typed_emitter2.TypedEmitter {
1117
935
  /**
1118
936
  * Create a DisTubeStream to play with {@link DisTubeVoice}
1119
- *
1120
937
  * @param url - Stream URL
1121
938
  * @param options - Stream options
1122
939
  */
1123
- constructor(url, { ffmpeg, seek, type }) {
940
+ constructor(url, options) {
941
+ if (typeof url !== "string" || !isURL(url)) {
942
+ throw new DisTubeError("INVALID_TYPE", "an URL", url);
943
+ }
944
+ if (!options || typeof options !== "object" || Array.isArray(options)) {
945
+ throw new DisTubeError("INVALID_TYPE", "object", options, "options");
946
+ }
1124
947
  super();
1125
- __publicField(this, "killed", false);
948
+ __privateAdd(this, _ffmpegPath);
949
+ __privateAdd(this, _opts);
1126
950
  __publicField(this, "process");
1127
951
  __publicField(this, "stream");
1128
- __publicField(this, "type");
1129
- __publicField(this, "url");
1130
- this.url = url;
1131
- this.type = !type ? import_voice2.StreamType.OggOpus : import_voice2.StreamType.Raw;
952
+ __publicField(this, "audioResource");
953
+ const { ffmpeg, seek } = options;
1132
954
  const opts = {
1133
955
  reconnect: 1,
1134
956
  reconnect_streamed: 1,
@@ -1140,34 +962,36 @@ var _DisTubeStream = class _DisTubeStream extends import_tiny_typed_emitter2.Typ
1140
962
  i: url,
1141
963
  ar: 48e3,
1142
964
  ac: 2,
1143
- ...ffmpeg.args.output
965
+ ...ffmpeg.args.output,
966
+ f: "s16le"
1144
967
  };
1145
- if (!type) {
1146
- opts.f = "opus";
1147
- opts.acodec = "libopus";
1148
- } else {
1149
- opts.f = "s16le";
1150
- }
1151
- if (typeof seek === "number" && seek > 0)
1152
- opts.ss = seek.toString();
1153
- if (typeof ffmpeg.args === "object")
1154
- Object.assign(opts, ffmpeg.args);
1155
- this.process = (0, import_child_process.spawn)(
1156
- ffmpeg.path,
1157
- [
1158
- ...Object.entries(opts).flatMap(
1159
- ([key, value]) => Array.isArray(value) ? value.filter(Boolean).map((v) => [`-${key}`, String(v)]) : value == null || value === false ? [] : [value === true ? `-${key}` : [`-${key}`, String(value)]]
1160
- ).flat(),
1161
- "pipe:1"
1162
- ],
1163
- { stdio: ["ignore", "pipe", "pipe"], shell: false, windowsHide: true }
1164
- ).on("error", (err) => {
968
+ if (typeof seek === "number" && seek > 0) opts.ss = seek.toString();
969
+ __privateSet(this, _ffmpegPath, ffmpeg.path);
970
+ __privateSet(this, _opts, [
971
+ ...Object.entries(opts).flatMap(
972
+ ([key, value]) => Array.isArray(value) ? value.filter(Boolean).map((v) => [`-${key}`, String(v)]) : value == null || value === false ? [] : [value === true ? `-${key}` : [`-${key}`, String(value)]]
973
+ ).flat(),
974
+ "pipe:1"
975
+ ]);
976
+ this.stream = new VolumeTransformer();
977
+ this.stream.on("close", () => this.kill()).on("error", (err) => {
978
+ this.debug(`[stream] error: ${err.message}`);
979
+ this.emit("error", err);
980
+ }).on("finish", () => this.debug("[stream] log: stream finished"));
981
+ this.audioResource = (0, import_voice2.createAudioResource)(this.stream, { inputType: import_voice2.StreamType.Raw, inlineVolume: false });
982
+ }
983
+ spawn() {
984
+ this.debug(`[process] spawn: ${__privateGet(this, _ffmpegPath)} ${__privateGet(this, _opts).join(" ")}`);
985
+ this.process = (0, import_child_process.spawn)(__privateGet(this, _ffmpegPath), __privateGet(this, _opts), {
986
+ stdio: ["ignore", "pipe", "pipe"],
987
+ shell: false,
988
+ windowsHide: true
989
+ }).on("error", (err) => {
1165
990
  this.debug(`[process] error: ${err.message}`);
1166
991
  this.emit("error", err);
1167
992
  }).on("exit", (code, signal) => {
1168
993
  this.debug(`[process] exit: code=${code ?? "unknown"} signal=${signal ?? "unknown"}`);
1169
- if (!code || [0, 255].includes(code))
1170
- return;
994
+ if (!code || [0, 255].includes(code)) return;
1171
995
  this.debug(`[process] error: ffmpeg exited with code ${code}`);
1172
996
  this.emit("error", new DisTubeError("FFMPEG_EXITED", code));
1173
997
  });
@@ -1175,17 +999,11 @@ var _DisTubeStream = class _DisTubeStream extends import_tiny_typed_emitter2.Typ
1175
999
  this.kill();
1176
1000
  throw new Error("Failed to create ffmpeg process");
1177
1001
  }
1178
- this.stream = new import_node_stream.PassThrough();
1179
- this.stream.on("close", () => this.kill()).on("error", (err) => {
1180
- this.debug(`[stream] error: ${err.message}`);
1181
- this.emit("error", err);
1182
- }).on("finish", () => this.debug("[stream] log: stream finished"));
1183
1002
  this.process.stdout.pipe(this.stream);
1184
1003
  this.process.stderr.setEncoding("utf8")?.on("data", (data) => {
1185
1004
  const lines = data.split(/\r\n|\r|\n/u);
1186
1005
  for (const line of lines) {
1187
- if (/^\s*$/.test(line))
1188
- continue;
1006
+ if (/^\s*$/.test(line)) continue;
1189
1007
  this.debug(`[ffmpeg] log: ${line}`);
1190
1008
  }
1191
1009
  });
@@ -1193,422 +1011,171 @@ var _DisTubeStream = class _DisTubeStream extends import_tiny_typed_emitter2.Typ
1193
1011
  debug(debug) {
1194
1012
  this.emit("debug", debug);
1195
1013
  }
1196
- kill() {
1197
- if (this.killed)
1198
- return;
1199
- this.process.kill("SIGKILL");
1200
- this.killed = true;
1014
+ setVolume(volume) {
1015
+ this.stream.vol = volume;
1201
1016
  }
1202
- /**
1203
- * Create a stream from a YouTube {@link Song}
1204
- *
1205
- * @param song - A YouTube Song
1206
- * @param options - options
1207
- */
1208
- static YouTube(song, options) {
1209
- if (song.source !== "youtube")
1210
- throw new DisTubeError("INVALID_TYPE", "youtube", song.source, "Song#source");
1211
- if (!song.formats?.length)
1212
- throw new DisTubeError("UNAVAILABLE_VIDEO");
1213
- if (!options || typeof options !== "object" || Array.isArray(options)) {
1214
- throw new DisTubeError("INVALID_TYPE", "object", options, "options");
1215
- }
1216
- const bestFormat = chooseBestVideoFormat(song);
1217
- if (!bestFormat)
1218
- throw new DisTubeError("UNPLAYABLE_FORMATS");
1219
- return new _DisTubeStream(bestFormat.url, options);
1017
+ kill() {
1018
+ if (!this.stream.destroyed) this.stream.destroy();
1019
+ if (this.process && !this.process.killed) this.process.kill("SIGKILL");
1220
1020
  }
1221
- /**
1222
- * Create a stream from a stream url
1223
- *
1224
- * @param url - stream url
1225
- * @param options - options
1226
- */
1227
- static DirectLink(url, options) {
1228
- if (typeof url !== "string" || !isURL(url)) {
1229
- throw new DisTubeError("INVALID_TYPE", "an URL", url);
1021
+ };
1022
+ _ffmpegPath = new WeakMap();
1023
+ _opts = new WeakMap();
1024
+ __name(_DisTubeStream, "DisTubeStream");
1025
+ var DisTubeStream = _DisTubeStream;
1026
+ var _VolumeTransformer = class _VolumeTransformer extends import_stream.Transform {
1027
+ constructor() {
1028
+ super(...arguments);
1029
+ __publicField(this, "buffer", Buffer.allocUnsafe(0));
1030
+ __publicField(this, "extrema", [-Math.pow(2, 16 - 1), Math.pow(2, 16 - 1) - 1]);
1031
+ __publicField(this, "vol", 1);
1032
+ }
1033
+ _transform(newChunk, _encoding, done) {
1034
+ const { vol } = this;
1035
+ if (vol === 1) {
1036
+ this.push(newChunk);
1037
+ done();
1038
+ return;
1230
1039
  }
1231
- if (!options || typeof options !== "object" || Array.isArray(options)) {
1232
- throw new DisTubeError("INVALID_TYPE", "object", options, "options");
1040
+ const bytes = 2;
1041
+ const chunk = Buffer.concat([this.buffer, newChunk]);
1042
+ const readableLength = Math.floor(chunk.length / bytes) * bytes;
1043
+ for (let i = 0; i < readableLength; i += bytes) {
1044
+ const value = chunk.readInt16LE(i);
1045
+ const clampedValue = Math.min(this.extrema[1], Math.max(this.extrema[0], value * vol));
1046
+ chunk.writeInt16LE(clampedValue, i);
1233
1047
  }
1234
- return new _DisTubeStream(url, options);
1048
+ this.buffer = chunk.subarray(readableLength);
1049
+ this.push(chunk.subarray(0, readableLength));
1050
+ done();
1235
1051
  }
1236
1052
  };
1237
- __name(_DisTubeStream, "DisTubeStream");
1238
- var DisTubeStream = _DisTubeStream;
1053
+ __name(_VolumeTransformer, "VolumeTransformer");
1054
+ var VolumeTransformer = _VolumeTransformer;
1239
1055
 
1240
1056
  // src/core/DisTubeHandler.ts
1241
- var import_ytpl = __toESM(require("@distube/ytpl"));
1242
- var import_ytdl_core = __toESM(require("@distube/ytdl-core"));
1243
- var import_tough_cookie = require("tough-cookie");
1244
- var _cookie;
1057
+ var import_undici = require("undici");
1058
+ var REDIRECT_CODES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
1059
+ var _DisTubeHandler_instances, searchSong_fn;
1245
1060
  var _DisTubeHandler = class _DisTubeHandler extends DisTubeBase {
1246
- constructor(distube) {
1247
- super(distube);
1248
- __privateAdd(this, _cookie, "");
1249
- const client = this.client;
1250
- if (this.options.leaveOnEmpty) {
1251
- client.on("voiceStateUpdate", (oldState) => {
1252
- if (!oldState?.channel)
1253
- return;
1254
- const queue = this.queues.get(oldState);
1255
- if (!queue) {
1256
- if (isVoiceChannelEmpty(oldState)) {
1257
- setTimeout(() => {
1258
- if (!this.queues.get(oldState) && isVoiceChannelEmpty(oldState))
1259
- this.voices.leave(oldState);
1260
- }, this.options.emptyCooldown * 1e3).unref();
1261
- }
1262
- return;
1263
- }
1264
- if (queue._emptyTimeout) {
1265
- clearTimeout(queue._emptyTimeout);
1266
- delete queue._emptyTimeout;
1267
- }
1268
- if (isVoiceChannelEmpty(oldState)) {
1269
- queue._emptyTimeout = setTimeout(() => {
1270
- delete queue._emptyTimeout;
1271
- if (isVoiceChannelEmpty(oldState)) {
1272
- queue.voice.leave();
1273
- this.emit("empty" /* EMPTY */, queue);
1274
- if (queue.stopped)
1275
- queue.remove();
1276
- }
1277
- }, this.options.emptyCooldown * 1e3).unref();
1278
- }
1279
- });
1280
- }
1281
- }
1282
- get ytdlOptions() {
1283
- const options = this.options.ytdlOptions;
1284
- if (this.options.youtubeCookie && this.options.youtubeCookie !== __privateGet(this, _cookie)) {
1285
- const cookies = __privateSet(this, _cookie, this.options.youtubeCookie);
1286
- if (typeof cookies === "string") {
1287
- console.warn(
1288
- "\x1B[33mWARNING:\x1B[0m You are using the old YouTube cookie format, please use the new one instead. (https://github.com/skick1234/DisTube/wiki/YouTube-Cookies)"
1289
- );
1290
- options.agent = import_ytdl_core.default.createAgent(
1291
- cookies.split(";").map((c) => import_tough_cookie.Cookie.parse(c)).filter(isTruthy)
1292
- );
1293
- } else {
1294
- options.agent = import_ytdl_core.default.createAgent(cookies);
1295
- }
1296
- }
1297
- return options;
1298
- }
1299
- get ytCookie() {
1300
- const agent = this.ytdlOptions.agent;
1301
- if (!agent)
1302
- return "";
1303
- const { jar } = agent;
1304
- return jar.getCookieStringSync("https://www.youtube.com");
1305
- }
1306
- /**
1307
- * @param url - url
1308
- * @param basic - getBasicInfo?
1309
- */
1310
- getYouTubeInfo(url, basic = false) {
1311
- if (basic)
1312
- return import_ytdl_core.default.getBasicInfo(url, this.ytdlOptions);
1313
- return import_ytdl_core.default.getInfo(url, this.ytdlOptions);
1061
+ constructor() {
1062
+ super(...arguments);
1063
+ __privateAdd(this, _DisTubeHandler_instances);
1314
1064
  }
1315
1065
  /**
1316
1066
  * Resolve a url or a supported object to a {@link Song} or {@link Playlist}
1317
- *
1318
1067
  * @throws {@link DisTubeError}
1319
- *
1320
- * @param song - URL | {@link Song}| {@link SearchResult} | {@link Playlist}
1068
+ * @param input - Resolvable input
1321
1069
  * @param options - Optional options
1322
- *
1323
1070
  * @returns Resolved
1324
1071
  */
1325
- async resolve(song, options = {}) {
1326
- if (song instanceof Song || song instanceof Playlist) {
1327
- if ("metadata" in options)
1328
- song.metadata = options.metadata;
1329
- if ("member" in options)
1330
- song.member = options.member;
1331
- return song;
1332
- }
1333
- if (song instanceof SearchResultVideo)
1334
- return new Song(song, options);
1335
- if (song instanceof SearchResultPlaylist)
1336
- return this.resolvePlaylist(song.url, options);
1337
- if (isObject(song)) {
1338
- if (!("url" in song) && !("id" in song))
1339
- throw new DisTubeError("CANNOT_RESOLVE_SONG", song);
1340
- return new Song(song, options);
1072
+ async resolve(input, options = {}) {
1073
+ if (input instanceof Song || input instanceof Playlist) {
1074
+ if ("metadata" in options) input.metadata = options.metadata;
1075
+ if ("member" in options) input.member = options.member;
1076
+ return input;
1077
+ }
1078
+ if (typeof input === "string") {
1079
+ if (isURL(input)) {
1080
+ const plugin = await this._getPluginFromURL(input) || await this._getPluginFromURL(await this.followRedirectLink(input));
1081
+ if (!plugin) throw new DisTubeError("NOT_SUPPORTED_URL");
1082
+ this.debug(`[${plugin.constructor.name}] Resolving song from url: ${input}`);
1083
+ return plugin.resolve(input, options);
1084
+ }
1085
+ try {
1086
+ const song = await __privateMethod(this, _DisTubeHandler_instances, searchSong_fn).call(this, input, options);
1087
+ if (song) return song;
1088
+ } catch {
1089
+ }
1341
1090
  }
1342
- if (import_ytpl.default.validateID(song))
1343
- return this.resolvePlaylist(song, options);
1344
- if (import_ytdl_core.default.validateURL(song))
1345
- return new Song(await this.getYouTubeInfo(song, true), options);
1346
- if (isURL(song)) {
1347
- for (const plugin of this.distube.extractorPlugins) {
1348
- if (await plugin.validate(song))
1349
- return plugin.resolve(song, options);
1350
- }
1351
- throw new DisTubeError("NOT_SUPPORTED_URL");
1352
- }
1353
- throw new DisTubeError("CANNOT_RESOLVE_SONG", song);
1354
- }
1355
- /**
1356
- * Resolve Song[] or YouTube playlist url to a Playlist
1357
- *
1358
- * @param playlist - Resolvable playlist
1359
- * @param options - Optional options
1360
- */
1361
- async resolvePlaylist(playlist, options = {}) {
1362
- const { member, source, metadata } = { source: "youtube", ...options };
1363
- if (playlist instanceof Playlist) {
1364
- if ("metadata" in options)
1365
- playlist.metadata = metadata;
1366
- if ("member" in options)
1367
- playlist.member = member;
1368
- return playlist;
1369
- }
1370
- if (typeof playlist === "string") {
1371
- const info = await (0, import_ytpl.default)(playlist, { limit: Infinity, requestOptions: { headers: { cookie: this.ytCookie } } });
1372
- const songs = info.items.filter((v) => !v.thumbnail.includes("no_thumbnail")).map((v) => new Song(v, { member, metadata }));
1373
- return new Playlist(
1374
- {
1375
- source,
1376
- songs,
1377
- member,
1378
- name: info.title,
1379
- url: info.url,
1380
- thumbnail: songs[0].thumbnail
1381
- },
1382
- { metadata }
1383
- );
1384
- }
1385
- return new Playlist(playlist, { member, properties: { source }, metadata });
1386
- }
1387
- /**
1388
- * Search for a song, fire {@link DisTube#error} if not found.
1389
- *
1390
- * @throws {@link DisTubeError}
1391
- *
1392
- * @param message - The original message from an user
1393
- * @param query - The query string
1394
- *
1395
- * @returns Song info
1396
- */
1397
- async searchSong(message, query) {
1398
- if (!isMessageInstance(message))
1399
- throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
1400
- if (typeof query !== "string")
1401
- throw new DisTubeError("INVALID_TYPE", "string", query, "query");
1402
- if (query.length === 0)
1403
- throw new DisTubeError("EMPTY_STRING", "query");
1404
- const limit = this.options.searchSongs > 1 ? this.options.searchSongs : 1;
1405
- const results = await this.distube.search(query, {
1406
- limit,
1407
- safeSearch: this.options.nsfw ? false : !isNsfwChannel(message.channel)
1408
- }).catch(() => {
1409
- if (!this.emit("searchNoResult" /* SEARCH_NO_RESULT */, message, query)) {
1410
- console.warn("searchNoResult event does not have any listeners! Emits `error` event instead.");
1411
- throw new DisTubeError("NO_RESULT");
1412
- }
1413
- });
1414
- if (!results)
1415
- return null;
1416
- return this.createSearchMessageCollector(message, results, query);
1417
- }
1418
- /**
1419
- * Create a message collector for selecting search results.
1420
- *
1421
- * Needed events: {@link DisTube#searchResult}, {@link DisTube#searchCancel},
1422
- * {@link DisTube#searchInvalidAnswer}, {@link DisTube#searchDone}.
1423
- *
1424
- * @throws {@link DisTubeError}
1425
- *
1426
- * @param message - The original message from an user
1427
- * @param results - The search results
1428
- * @param query - The query string
1429
- *
1430
- * @returns Selected result
1431
- */
1432
- async createSearchMessageCollector(message, results, query) {
1433
- if (!isMessageInstance(message))
1434
- throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
1435
- if (!Array.isArray(results) || results.length === 0) {
1436
- throw new DisTubeError("INVALID_TYPE", "Array<SearchResult|Song|Playlist>", results, "results");
1437
- }
1438
- if (this.options.searchSongs > 1) {
1439
- const searchEvents = [
1440
- "searchNoResult" /* SEARCH_NO_RESULT */,
1441
- "searchResult" /* SEARCH_RESULT */,
1442
- "searchCancel" /* SEARCH_CANCEL */,
1443
- "searchInvalidAnswer" /* SEARCH_INVALID_ANSWER */,
1444
- "searchDone" /* SEARCH_DONE */
1445
- ];
1446
- for (const evn of searchEvents) {
1447
- if (this.distube.listenerCount(evn) === 0) {
1448
- console.warn(`"searchSongs" option is disabled due to missing "${evn}" listener.`);
1449
- console.warn(
1450
- `If you don't want to use "${evn}" event, simply add an empty listener (not recommended):
1451
- <DisTube>.on("${evn}", () => {})`
1452
- );
1453
- this.options.searchSongs = 0;
1454
- }
1455
- }
1456
- }
1457
- const limit = this.options.searchSongs > 1 ? this.options.searchSongs : 1;
1458
- let result = results[0];
1459
- if (limit > 1) {
1460
- results.splice(limit);
1461
- this.emit("searchResult" /* SEARCH_RESULT */, message, results, query);
1462
- const answers = await message.channel.awaitMessages({
1463
- filter: (m) => m.author.id === message.author.id,
1464
- max: 1,
1465
- time: this.options.searchCooldown * 1e3,
1466
- errors: ["time"]
1467
- }).catch(() => void 0);
1468
- const ans = answers?.first();
1469
- if (!ans) {
1470
- this.emit("searchCancel" /* SEARCH_CANCEL */, message, query);
1471
- return null;
1472
- }
1473
- const index = parseInt(ans.content, 10);
1474
- if (isNaN(index) || index > results.length || index < 1) {
1475
- this.emit("searchInvalidAnswer" /* SEARCH_INVALID_ANSWER */, message, ans, query);
1476
- return null;
1477
- }
1478
- this.emit("searchDone" /* SEARCH_DONE */, message, ans, query);
1479
- result = results[index - 1];
1480
- }
1481
- return result;
1482
- }
1483
- /**
1484
- * Play or add a {@link Playlist} to the queue.
1485
- *
1486
- * @throws {@link DisTubeError}
1487
- *
1488
- * @param voiceChannel - A voice channel
1489
- * @param playlist - A YouTube playlist url | a Playlist
1490
- * @param options - Optional options
1491
- */
1492
- async playPlaylist(voiceChannel, playlist, options = {}) {
1493
- const { textChannel, skip } = { skip: false, ...options };
1494
- const position = Number(options.position) || (skip ? 1 : 0);
1495
- if (!(playlist instanceof Playlist))
1496
- throw new DisTubeError("INVALID_TYPE", "Playlist", playlist, "playlist");
1497
- const queue = this.queues.get(voiceChannel);
1498
- const isNsfw = isNsfwChannel(queue?.textChannel || textChannel);
1499
- if (!this.options.nsfw && !isNsfw)
1500
- playlist.songs = playlist.songs.filter((s) => !s.age_restricted);
1501
- if (!playlist.songs.length) {
1502
- if (!this.options.nsfw && !isNsfw)
1503
- throw new DisTubeError("EMPTY_FILTERED_PLAYLIST");
1504
- throw new DisTubeError("EMPTY_PLAYLIST");
1505
- }
1506
- if (queue) {
1507
- if (this.options.joinNewVoiceChannel)
1508
- queue.voice.channel = voiceChannel;
1509
- queue.addToQueue(playlist.songs, position);
1510
- if (skip)
1511
- queue.skip();
1512
- else
1513
- this.emit("addList" /* ADD_LIST */, queue, playlist);
1514
- } else {
1515
- const newQueue = await this.queues.create(voiceChannel, playlist.songs, textChannel);
1516
- if (newQueue instanceof Queue) {
1517
- if (this.options.emitAddListWhenCreatingQueue)
1518
- this.emit("addList" /* ADD_LIST */, newQueue, playlist);
1519
- this.emit("playSong" /* PLAY_SONG */, newQueue, newQueue.songs[0]);
1520
- }
1521
- }
1522
- }
1523
- /**
1524
- * Play or add a {@link Song} to the queue.
1525
- *
1526
- * @throws {@link DisTubeError}
1527
- *
1528
- * @param voiceChannel - A voice channel
1529
- * @param song - A YouTube playlist url | a Playlist
1530
- * @param options - Optional options
1531
- */
1532
- async playSong(voiceChannel, song, options = {}) {
1533
- if (!(song instanceof Song))
1534
- throw new DisTubeError("INVALID_TYPE", "Song", song, "song");
1535
- const { textChannel, skip } = { skip: false, ...options };
1536
- const position = Number(options.position) || (skip ? 1 : 0);
1537
- const queue = this.queues.get(voiceChannel);
1538
- if (!this.options.nsfw && song.age_restricted && !isNsfwChannel(queue?.textChannel || textChannel)) {
1539
- throw new DisTubeError("NON_NSFW");
1540
- }
1541
- if (queue) {
1542
- if (this.options.joinNewVoiceChannel)
1543
- queue.voice.channel = voiceChannel;
1544
- queue.addToQueue(song, position);
1545
- if (skip)
1546
- queue.skip();
1547
- else
1548
- this.emit("addSong" /* ADD_SONG */, queue, song);
1549
- } else {
1550
- const newQueue = await this.queues.create(voiceChannel, song, textChannel);
1551
- if (newQueue instanceof Queue) {
1552
- if (this.options.emitAddSongWhenCreatingQueue)
1553
- this.emit("addSong" /* ADD_SONG */, newQueue, song);
1554
- this.emit("playSong" /* PLAY_SONG */, newQueue, song);
1091
+ throw new DisTubeError("CANNOT_RESOLVE_SONG", input);
1092
+ }
1093
+ async _getPluginFromURL(url) {
1094
+ for (const plugin of this.plugins) if (await plugin.validate(url)) return plugin;
1095
+ return null;
1096
+ }
1097
+ async _getPluginFromSong(song, types, validate = true) {
1098
+ if (!types || types.includes(song.plugin?.type)) return song.plugin;
1099
+ if (!song.url) return null;
1100
+ for (const plugin of this.plugins) {
1101
+ if ((!types || types.includes(plugin?.type)) && (!validate || await plugin.validate(song.url))) {
1102
+ return plugin;
1555
1103
  }
1556
1104
  }
1105
+ return null;
1557
1106
  }
1558
1107
  /**
1559
1108
  * Get {@link Song}'s stream info and attach it to the song.
1560
- *
1561
1109
  * @param song - A Song
1562
1110
  */
1563
1111
  async attachStreamInfo(song) {
1564
- const { url, source } = song;
1565
- if (source === "youtube") {
1566
- song._patchYouTube(await this.handler.getYouTubeInfo(url));
1112
+ if (song.stream.playFromSource) {
1113
+ if (song.stream.url) return;
1114
+ this.debug(`[DisTubeHandler] Getting stream info: ${song}`);
1115
+ const plugin = await this._getPluginFromSong(song, ["extractor" /* EXTRACTOR */, "playable-extractor" /* PLAYABLE_EXTRACTOR */]);
1116
+ if (!plugin) throw new DisTubeError("NOT_SUPPORTED_SONG", song.toString());
1117
+ this.debug(`[${plugin.constructor.name}] Getting stream URL: ${song}`);
1118
+ song.stream.url = await plugin.getStreamURL(song);
1119
+ if (!song.stream.url) throw new DisTubeError("CANNOT_GET_STREAM_URL", song.toString());
1567
1120
  } else {
1568
- for (const plugin of [...this.distube.extractorPlugins, ...this.distube.customPlugins]) {
1569
- if (await plugin.validate(url)) {
1570
- const info = [plugin.getStreamURL(url), plugin.getRelatedSongs(url)];
1571
- const result = await Promise.all(info);
1572
- song.streamURL = result[0];
1573
- song.related = result[1];
1574
- break;
1575
- }
1121
+ if (song.stream.song?.stream?.playFromSource && song.stream.song.stream.url) return;
1122
+ this.debug(`[DisTubeHandler] Getting stream info: ${song}`);
1123
+ const plugin = await this._getPluginFromSong(song, ["info-extractor" /* INFO_EXTRACTOR */]);
1124
+ if (!plugin) throw new DisTubeError("NOT_SUPPORTED_SONG", song.toString());
1125
+ this.debug(`[${plugin.constructor.name}] Creating search query for: ${song}`);
1126
+ const query = await plugin.createSearchQuery(song);
1127
+ if (!query) throw new DisTubeError("CANNOT_GET_SEARCH_QUERY", song.toString());
1128
+ const altSong = await __privateMethod(this, _DisTubeHandler_instances, searchSong_fn).call(this, query, { metadata: song.metadata, member: song.member }, true);
1129
+ if (!altSong || !altSong.stream.playFromSource) throw new DisTubeError("NO_RESULT", query || song.toString());
1130
+ song.stream.song = altSong;
1131
+ }
1132
+ }
1133
+ async followRedirectLink(url, maxRedirect = 5) {
1134
+ if (maxRedirect === 0) return url;
1135
+ const res = await (0, import_undici.request)(url, {
1136
+ method: "HEAD",
1137
+ headers: {
1138
+ "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"
1576
1139
  }
1140
+ });
1141
+ if (REDIRECT_CODES.has(res.statusCode ?? 200)) {
1142
+ let location = res.headers.location;
1143
+ if (typeof location !== "string") location = location?.[0] ?? url;
1144
+ return this.followRedirectLink(location, --maxRedirect);
1577
1145
  }
1146
+ return url;
1578
1147
  }
1579
1148
  };
1580
- _cookie = new WeakMap();
1149
+ _DisTubeHandler_instances = new WeakSet();
1150
+ searchSong_fn = /* @__PURE__ */ __name(async function(query, options = {}, getStreamURL = false) {
1151
+ for (const plugin of this.plugins) {
1152
+ if (plugin.type === "extractor" /* EXTRACTOR */) {
1153
+ this.debug(`[${plugin.constructor.name}] Searching for song: ${query}`);
1154
+ const result = await plugin.searchSong(query, options);
1155
+ if (result) {
1156
+ if (getStreamURL && result.stream.playFromSource) result.stream.url = await plugin.getStreamURL(result);
1157
+ return result;
1158
+ }
1159
+ }
1160
+ }
1161
+ return null;
1162
+ }, "#searchSong");
1581
1163
  __name(_DisTubeHandler, "DisTubeHandler");
1582
1164
  var DisTubeHandler = _DisTubeHandler;
1583
1165
 
1584
1166
  // src/core/DisTubeOptions.ts
1585
- var _validateOptions, validateOptions_fn, _ffmpegOption, ffmpegOption_fn;
1167
+ var _Options_instances, validateOptions_fn, ffmpegOption_fn;
1586
1168
  var _Options = class _Options {
1587
1169
  constructor(options) {
1588
- __privateAdd(this, _validateOptions);
1589
- __privateAdd(this, _ffmpegOption);
1170
+ __privateAdd(this, _Options_instances);
1590
1171
  __publicField(this, "plugins");
1591
1172
  __publicField(this, "emitNewSongOnly");
1592
- __publicField(this, "leaveOnFinish");
1593
- __publicField(this, "leaveOnStop");
1594
- __publicField(this, "leaveOnEmpty");
1595
- __publicField(this, "emptyCooldown");
1596
1173
  __publicField(this, "savePreviousSongs");
1597
- __publicField(this, "searchSongs");
1598
- __publicField(this, "searchCooldown");
1599
- __publicField(this, "youtubeCookie");
1600
1174
  __publicField(this, "customFilters");
1601
- __publicField(this, "ytdlOptions");
1602
1175
  __publicField(this, "nsfw");
1603
1176
  __publicField(this, "emitAddSongWhenCreatingQueue");
1604
1177
  __publicField(this, "emitAddListWhenCreatingQueue");
1605
1178
  __publicField(this, "joinNewVoiceChannel");
1606
- __publicField(this, "streamType");
1607
- __publicField(this, "directLink");
1608
- /** @deprecated */
1609
- __publicField(this, "ffmpegPath");
1610
- /** @deprecated */
1611
- __publicField(this, "ffmpegDefaultArgs");
1612
1179
  __publicField(this, "ffmpeg");
1613
1180
  if (typeof options !== "object" || Array.isArray(options)) {
1614
1181
  throw new DisTubeError("INVALID_TYPE", "object", options, "DisTubeOptions");
@@ -1616,53 +1183,34 @@ var _Options = class _Options {
1616
1183
  const opts = { ...defaultOptions, ...options };
1617
1184
  this.plugins = opts.plugins;
1618
1185
  this.emitNewSongOnly = opts.emitNewSongOnly;
1619
- this.leaveOnEmpty = opts.leaveOnEmpty;
1620
- this.leaveOnFinish = opts.leaveOnFinish;
1621
- this.leaveOnStop = opts.leaveOnStop;
1622
1186
  this.savePreviousSongs = opts.savePreviousSongs;
1623
- this.searchSongs = opts.searchSongs;
1624
- this.youtubeCookie = opts.youtubeCookie;
1625
1187
  this.customFilters = opts.customFilters;
1626
- this.ytdlOptions = opts.ytdlOptions;
1627
- this.searchCooldown = opts.searchCooldown;
1628
- this.emptyCooldown = opts.emptyCooldown;
1629
1188
  this.nsfw = opts.nsfw;
1630
1189
  this.emitAddSongWhenCreatingQueue = opts.emitAddSongWhenCreatingQueue;
1631
1190
  this.emitAddListWhenCreatingQueue = opts.emitAddListWhenCreatingQueue;
1632
1191
  this.joinNewVoiceChannel = opts.joinNewVoiceChannel;
1633
- this.streamType = opts.streamType;
1634
- this.directLink = opts.directLink;
1635
- this.ffmpeg = __privateMethod(this, _ffmpegOption, ffmpegOption_fn).call(this, options);
1192
+ this.ffmpeg = __privateMethod(this, _Options_instances, ffmpegOption_fn).call(this, options);
1636
1193
  checkInvalidKey(opts, this, "DisTubeOptions");
1637
- __privateMethod(this, _validateOptions, validateOptions_fn).call(this);
1194
+ __privateMethod(this, _Options_instances, validateOptions_fn).call(this);
1638
1195
  }
1639
1196
  };
1640
- _validateOptions = new WeakSet();
1197
+ _Options_instances = new WeakSet();
1641
1198
  validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
1642
1199
  const booleanOptions = /* @__PURE__ */ new Set([
1643
1200
  "emitNewSongOnly",
1644
- "leaveOnEmpty",
1645
- "leaveOnFinish",
1646
- "leaveOnStop",
1647
1201
  "savePreviousSongs",
1648
1202
  "joinNewVoiceChannel",
1649
1203
  "nsfw",
1650
1204
  "emitAddSongWhenCreatingQueue",
1651
- "emitAddListWhenCreatingQueue",
1652
- "directLink"
1205
+ "emitAddListWhenCreatingQueue"
1653
1206
  ]);
1654
- const numberOptions = /* @__PURE__ */ new Set(["searchCooldown", "emptyCooldown", "searchSongs"]);
1207
+ const numberOptions = /* @__PURE__ */ new Set();
1655
1208
  const stringOptions = /* @__PURE__ */ new Set();
1656
- const objectOptions = /* @__PURE__ */ new Set(["customFilters", "ytdlOptions", "ffmpeg"]);
1657
- const optionalOptions = /* @__PURE__ */ new Set(["youtubeCookie", "customFilters"]);
1209
+ const objectOptions = /* @__PURE__ */ new Set(["customFilters", "ffmpeg"]);
1210
+ const optionalOptions = /* @__PURE__ */ new Set(["customFilters"]);
1658
1211
  for (const [key, value] of Object.entries(options)) {
1659
- if (value === void 0 && optionalOptions.has(key))
1660
- continue;
1661
- if (key === "youtubeCookie" && !Array.isArray(value) && typeof value !== "string") {
1662
- throw new DisTubeError("INVALID_TYPE", ["Array<Cookie>", "string"], value, `DisTubeOptions.${key}`);
1663
- } else if (key === "streamType" && (typeof value !== "number" || isNaN(value) || !StreamType[value])) {
1664
- throw new DisTubeError("INVALID_TYPE", "StreamType", value, `DisTubeOptions.${key}`);
1665
- } else if (key === "plugins" && !Array.isArray(value)) {
1212
+ if (value === void 0 && optionalOptions.has(key)) continue;
1213
+ if (key === "plugins" && !Array.isArray(value)) {
1666
1214
  throw new DisTubeError("INVALID_TYPE", "Array<Plugin>", value, `DisTubeOptions.${key}`);
1667
1215
  } else if (booleanOptions.has(key)) {
1668
1216
  if (typeof value !== "boolean") {
@@ -1683,26 +1231,31 @@ validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
1683
1231
  }
1684
1232
  }
1685
1233
  }, "#validateOptions");
1686
- _ffmpegOption = new WeakSet();
1687
1234
  ffmpegOption_fn = /* @__PURE__ */ __name(function(opts) {
1688
- let path;
1689
1235
  const args = { global: {}, input: {}, output: {} };
1690
- if (opts.ffmpegPath) {
1691
- console.warn("`DisTubeOptions.ffmpegPath` is deprecated. Use `ffmpeg.path` instead.");
1692
- path = opts.ffmpegPath;
1693
- }
1694
- if (opts.ffmpegDefaultArgs) {
1695
- console.warn("`DisTubeOptions.ffmpegDefaultArgs` is deprecated. Use `ffmpeg.args` instead.");
1696
- args.global = opts.ffmpegDefaultArgs;
1697
- }
1698
- path ??= opts.ffmpeg?.path ?? `ffmpeg${process.platform === "win32" ? ".exe" : ""}`;
1699
1236
  if (opts.ffmpeg?.args) {
1700
- if (opts.ffmpeg.args.global)
1701
- args.global = opts.ffmpeg.args.global;
1702
- if (opts.ffmpeg.args.input)
1703
- args.input = opts.ffmpeg.args.input;
1704
- if (opts.ffmpeg.args.output)
1705
- args.output = opts.ffmpeg.args.output;
1237
+ if (opts.ffmpeg.args.global) args.global = opts.ffmpeg.args.global;
1238
+ if (opts.ffmpeg.args.input) args.input = opts.ffmpeg.args.input;
1239
+ if (opts.ffmpeg.args.output) args.output = opts.ffmpeg.args.output;
1240
+ }
1241
+ const path = opts.ffmpeg?.path ?? "ffmpeg";
1242
+ if (typeof path !== "string") {
1243
+ throw new DisTubeError("INVALID_TYPE", "string", path, "DisTubeOptions.ffmpeg.path");
1244
+ }
1245
+ for (const [key, value] of Object.entries(args)) {
1246
+ if (typeof value !== "object" || Array.isArray(value)) {
1247
+ throw new DisTubeError("INVALID_TYPE", "object", value, `DisTubeOptions.ffmpeg.${key}`);
1248
+ }
1249
+ for (const [k, v] of Object.entries(value)) {
1250
+ if (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean" && !Array.isArray(v) && v !== null && v !== void 0) {
1251
+ throw new DisTubeError(
1252
+ "INVALID_TYPE",
1253
+ ["string", "number", "boolean", "Array<string | null | undefined>", "null", "undefined"],
1254
+ v,
1255
+ `DisTubeOptions.ffmpeg.${key}.${k}`
1256
+ );
1257
+ }
1258
+ }
1706
1259
  }
1707
1260
  return { path, args };
1708
1261
  }, "#ffmpegOption");
@@ -1734,8 +1287,7 @@ var _GuildIdManager = class _GuildIdManager extends BaseManager {
1734
1287
  add(idOrInstance, data) {
1735
1288
  const id = resolveGuildId(idOrInstance);
1736
1289
  const existing = this.get(id);
1737
- if (existing)
1738
- return this;
1290
+ if (existing) return this;
1739
1291
  this.collection.set(id, data);
1740
1292
  return this;
1741
1293
  }
@@ -1756,16 +1308,7 @@ var GuildIdManager = _GuildIdManager;
1756
1308
  var import_voice3 = require("@discordjs/voice");
1757
1309
  var _DisTubeVoiceManager = class _DisTubeVoiceManager extends GuildIdManager {
1758
1310
  /**
1759
- * Get a {@link DisTubeVoice}.
1760
- *
1761
- * @param guild - The queue resolvable to resolve
1762
- */
1763
- /**
1764
- * Collection of {@link DisTubeVoice}.
1765
- */
1766
- /**
1767
- * Create a {@link DisTubeVoice}
1768
- *
1311
+ * Create a {@link DisTubeVoice} instance
1769
1312
  * @param channel - A voice channel to join
1770
1313
  */
1771
1314
  create(channel) {
@@ -1774,22 +1317,22 @@ var _DisTubeVoiceManager = class _DisTubeVoiceManager extends GuildIdManager {
1774
1317
  existing.channel = channel;
1775
1318
  return existing;
1776
1319
  }
1320
+ if ((0, import_voice3.getVoiceConnection)(resolveGuildId(channel), this.client.user?.id) || (0, import_voice3.getVoiceConnection)(resolveGuildId(channel))) {
1321
+ throw new DisTubeError("VOICE_ALREADY_CREATED");
1322
+ }
1777
1323
  return new DisTubeVoice(this, channel);
1778
1324
  }
1779
1325
  /**
1780
- * Join a voice channel
1781
- *
1326
+ * Join a voice channel and wait until the connection is ready
1782
1327
  * @param channel - A voice channel to join
1783
1328
  */
1784
1329
  join(channel) {
1785
1330
  const existing = this.get(channel.guildId);
1786
- if (existing)
1787
- return existing.join(channel);
1331
+ if (existing) return existing.join(channel);
1788
1332
  return this.create(channel).join();
1789
1333
  }
1790
1334
  /**
1791
1335
  * Leave the connected voice channel in a guild
1792
- *
1793
1336
  * @param guild - Queue Resolvable
1794
1337
  */
1795
1338
  leave(guild) {
@@ -1808,38 +1351,33 @@ __name(_DisTubeVoiceManager, "DisTubeVoiceManager");
1808
1351
  var DisTubeVoiceManager = _DisTubeVoiceManager;
1809
1352
 
1810
1353
  // src/core/manager/FilterManager.ts
1811
- var _resolve, resolve_fn, _apply, apply_fn, _removeFn, removeFn_fn;
1354
+ var _FilterManager_instances, resolve_fn, apply_fn, removeFn_fn;
1812
1355
  var _FilterManager = class _FilterManager extends BaseManager {
1813
1356
  constructor(queue) {
1814
1357
  super(queue.distube);
1815
- __privateAdd(this, _resolve);
1816
- __privateAdd(this, _apply);
1817
- __privateAdd(this, _removeFn);
1358
+ __privateAdd(this, _FilterManager_instances);
1818
1359
  /**
1819
- * Collection of {@link Filter}.
1360
+ * The queue to manage
1820
1361
  */
1821
1362
  __publicField(this, "queue");
1822
1363
  this.queue = queue;
1823
1364
  }
1824
1365
  /**
1825
1366
  * Enable a filter or multiple filters to the manager
1826
- *
1827
1367
  * @param filterOrFilters - The filter or filters to enable
1828
1368
  * @param override - Wether or not override the applied filter with new filter value
1829
1369
  */
1830
1370
  add(filterOrFilters, override = false) {
1831
1371
  if (Array.isArray(filterOrFilters)) {
1832
1372
  for (const filter of filterOrFilters) {
1833
- const ft = __privateMethod(this, _resolve, resolve_fn).call(this, filter);
1834
- if (override || !this.has(ft))
1835
- this.collection.set(ft.name, ft);
1373
+ const ft = __privateMethod(this, _FilterManager_instances, resolve_fn).call(this, filter);
1374
+ if (override || !this.has(ft)) this.collection.set(ft.name, ft);
1836
1375
  }
1837
1376
  } else {
1838
- const ft = __privateMethod(this, _resolve, resolve_fn).call(this, filterOrFilters);
1839
- if (override || !this.has(ft))
1840
- this.collection.set(ft.name, ft);
1377
+ const ft = __privateMethod(this, _FilterManager_instances, resolve_fn).call(this, filterOrFilters);
1378
+ if (override || !this.has(ft)) this.collection.set(ft.name, ft);
1841
1379
  }
1842
- __privateMethod(this, _apply, apply_fn).call(this);
1380
+ __privateMethod(this, _FilterManager_instances, apply_fn).call(this);
1843
1381
  return this;
1844
1382
  }
1845
1383
  /**
@@ -1850,40 +1388,34 @@ var _FilterManager = class _FilterManager extends BaseManager {
1850
1388
  }
1851
1389
  /**
1852
1390
  * Set the filters applied to the manager
1853
- *
1854
1391
  * @param filters - The filters to apply
1855
1392
  */
1856
1393
  set(filters) {
1857
- if (!Array.isArray(filters))
1858
- throw new DisTubeError("INVALID_TYPE", "Array<FilterResolvable>", filters, "filters");
1394
+ if (!Array.isArray(filters)) throw new DisTubeError("INVALID_TYPE", "Array<FilterResolvable>", filters, "filters");
1859
1395
  this.collection.clear();
1860
1396
  for (const f of filters) {
1861
- const filter = __privateMethod(this, _resolve, resolve_fn).call(this, f);
1397
+ const filter = __privateMethod(this, _FilterManager_instances, resolve_fn).call(this, f);
1862
1398
  this.collection.set(filter.name, filter);
1863
1399
  }
1864
- __privateMethod(this, _apply, apply_fn).call(this);
1400
+ __privateMethod(this, _FilterManager_instances, apply_fn).call(this);
1865
1401
  return this;
1866
1402
  }
1867
1403
  /**
1868
1404
  * Disable a filter or multiple filters
1869
- *
1870
1405
  * @param filterOrFilters - The filter or filters to disable
1871
1406
  */
1872
1407
  remove(filterOrFilters) {
1873
- if (Array.isArray(filterOrFilters))
1874
- filterOrFilters.forEach((f) => __privateMethod(this, _removeFn, removeFn_fn).call(this, f));
1875
- else
1876
- __privateMethod(this, _removeFn, removeFn_fn).call(this, filterOrFilters);
1877
- __privateMethod(this, _apply, apply_fn).call(this);
1408
+ if (Array.isArray(filterOrFilters)) filterOrFilters.forEach((f) => __privateMethod(this, _FilterManager_instances, removeFn_fn).call(this, f));
1409
+ else __privateMethod(this, _FilterManager_instances, removeFn_fn).call(this, filterOrFilters);
1410
+ __privateMethod(this, _FilterManager_instances, apply_fn).call(this);
1878
1411
  return this;
1879
1412
  }
1880
1413
  /**
1881
1414
  * Check whether a filter enabled or not
1882
- *
1883
1415
  * @param filter - The filter to check
1884
1416
  */
1885
1417
  has(filter) {
1886
- return this.collection.has(typeof filter === "string" ? filter : __privateMethod(this, _resolve, resolve_fn).call(this, filter).name);
1418
+ return this.collection.has(typeof filter === "string" ? filter : __privateMethod(this, _FilterManager_instances, resolve_fn).call(this, filter).name);
1887
1419
  }
1888
1420
  /**
1889
1421
  * Array of enabled filter names
@@ -1904,7 +1436,7 @@ var _FilterManager = class _FilterManager extends BaseManager {
1904
1436
  return this.names.toString();
1905
1437
  }
1906
1438
  };
1907
- _resolve = new WeakSet();
1439
+ _FilterManager_instances = new WeakSet();
1908
1440
  resolve_fn = /* @__PURE__ */ __name(function(filter) {
1909
1441
  if (typeof filter === "object" && typeof filter.name === "string" && typeof filter.value === "string") {
1910
1442
  return filter;
@@ -1917,232 +1449,179 @@ resolve_fn = /* @__PURE__ */ __name(function(filter) {
1917
1449
  }
1918
1450
  throw new DisTubeError("INVALID_TYPE", "FilterResolvable", filter, "filter");
1919
1451
  }, "#resolve");
1920
- _apply = new WeakSet();
1921
1452
  apply_fn = /* @__PURE__ */ __name(function() {
1922
- this.queue.beginTime = this.queue.currentTime;
1923
- this.queues.playSong(this.queue);
1453
+ this.queue._beginTime = this.queue.currentTime;
1454
+ this.queue.play(false);
1924
1455
  }, "#apply");
1925
- _removeFn = new WeakSet();
1926
1456
  removeFn_fn = /* @__PURE__ */ __name(function(f) {
1927
- return this.collection.delete(__privateMethod(this, _resolve, resolve_fn).call(this, f).name);
1457
+ return this.collection.delete(__privateMethod(this, _FilterManager_instances, resolve_fn).call(this, f).name);
1928
1458
  }, "#removeFn");
1929
1459
  __name(_FilterManager, "FilterManager");
1930
1460
  var FilterManager = _FilterManager;
1931
1461
 
1932
1462
  // src/core/manager/QueueManager.ts
1933
- var _voiceEventHandler, voiceEventHandler_fn, _emitPlaySong, emitPlaySong_fn, _handleSongFinish, handleSongFinish_fn, _handlePlayingError, handlePlayingError_fn;
1463
+ var _QueueManager_instances, voiceEventHandler_fn, emitPlaySong_fn, handleSongFinish_fn, handlePlayingError_fn;
1934
1464
  var _QueueManager = class _QueueManager extends GuildIdManager {
1935
1465
  constructor() {
1936
1466
  super(...arguments);
1937
- /**
1938
- * Get a Queue from this QueueManager.
1939
- *
1940
- * @param guild - Resolvable thing from a guild
1941
- */
1942
- /**
1943
- * Listen to DisTubeVoice events and handle the Queue
1944
- *
1945
- * @param queue - Queue
1946
- */
1947
- __privateAdd(this, _voiceEventHandler);
1948
- /**
1949
- * Whether or not emit playSong event
1950
- *
1951
- * @param queue - Queue
1952
- */
1953
- __privateAdd(this, _emitPlaySong);
1954
- /**
1955
- * Handle the queue when a Song finish
1956
- *
1957
- * @param queue - queue
1958
- */
1959
- __privateAdd(this, _handleSongFinish);
1960
- /**
1961
- * Handle error while playing
1962
- *
1963
- * @param queue - queue
1964
- * @param error - error
1965
- */
1966
- __privateAdd(this, _handlePlayingError);
1467
+ __privateAdd(this, _QueueManager_instances);
1967
1468
  }
1968
- /**
1969
- * Collection of {@link Queue}.
1970
- */
1971
1469
  /**
1972
1470
  * Create a {@link Queue}
1973
- *
1974
1471
  * @param channel - A voice channel
1975
- * @param song - First song
1976
1472
  * @param textChannel - Default text channel
1977
- *
1978
1473
  * @returns Returns `true` if encounter an error
1979
1474
  */
1980
- async create(channel, song, textChannel) {
1981
- if (this.has(channel.guildId))
1982
- throw new DisTubeError("QUEUE_EXIST");
1475
+ async create(channel, textChannel) {
1476
+ if (this.has(channel.guildId)) throw new DisTubeError("QUEUE_EXIST");
1477
+ this.debug(`[QueueManager] Creating queue for guild: ${channel.guildId}`);
1983
1478
  const voice = this.voices.create(channel);
1984
- const queue = new Queue(this.distube, voice, song, textChannel);
1479
+ const queue = new Queue(this.distube, voice, textChannel);
1985
1480
  await queue._taskQueue.queuing();
1986
1481
  try {
1987
1482
  checkFFmpeg(this.distube);
1483
+ this.debug(`[QueueManager] Joining voice channel: ${channel.id}`);
1988
1484
  await voice.join();
1989
- __privateMethod(this, _voiceEventHandler, voiceEventHandler_fn).call(this, queue);
1485
+ __privateMethod(this, _QueueManager_instances, voiceEventHandler_fn).call(this, queue);
1990
1486
  this.add(queue.id, queue);
1991
1487
  this.emit("initQueue" /* INIT_QUEUE */, queue);
1992
- const err = await this.playSong(queue);
1993
- return err || queue;
1488
+ return queue;
1994
1489
  } finally {
1995
1490
  queue._taskQueue.resolve();
1996
1491
  }
1997
1492
  }
1998
1493
  /**
1999
- * Create a ytdl stream
2000
- *
2001
- * @param queue - Queue
2002
- */
2003
- createStream(queue) {
2004
- const song = queue.songs[0];
2005
- const { duration, source, streamURL } = song;
2006
- const streamOptions = {
2007
- ffmpeg: {
2008
- path: this.options.ffmpeg.path,
2009
- args: {
2010
- global: { ...this.options.ffmpeg.args.global },
2011
- input: { ...this.options.ffmpeg.args.input },
2012
- output: { ...this.options.ffmpeg.args.output, ...queue.filters.ffmpegArgs }
2013
- }
2014
- },
2015
- seek: duration ? queue.beginTime : void 0,
2016
- type: this.options.streamType
2017
- };
2018
- if (source === "youtube")
2019
- return DisTubeStream.YouTube(song, streamOptions);
2020
- if (!streamURL)
2021
- throw new Error("No streamURL, something went wrong");
2022
- return DisTubeStream.DirectLink(streamURL, streamOptions);
2023
- }
2024
- /**
2025
- * Play a song on voice connection
2026
- *
2027
- * @param queue - The guild queue
2028
- *
2029
- * @returns error?
1494
+ * Play a song on voice connection with queue properties
1495
+ * @param queue - The guild queue to play
1496
+ * @param emitPlaySong - Whether or not emit {@link Events.PLAY_SONG} event
2030
1497
  */
2031
- async playSong(queue) {
2032
- if (!queue)
2033
- return true;
1498
+ async playSong(queue, emitPlaySong = true) {
1499
+ if (!queue) return;
2034
1500
  if (queue.stopped || !queue.songs.length) {
2035
1501
  queue.stop();
2036
- return true;
1502
+ return;
2037
1503
  }
2038
1504
  try {
2039
1505
  const song = queue.songs[0];
1506
+ this.debug(`[${queue.id}] Getting stream from: ${song}`);
2040
1507
  await this.handler.attachStreamInfo(song);
2041
- if (queue.stopped || !queue.songs.length) {
2042
- queue.stop();
2043
- return true;
2044
- }
2045
- const stream = this.createStream(queue);
2046
- stream.on("debug", (data) => this.emit("ffmpegDebug" /* FFMPEG_DEBUG */, `[${queue.id}]: ${data}`));
2047
- queue.voice.play(stream);
2048
- song.streamURL = stream.url;
2049
- return false;
1508
+ const willPlaySong = song.stream.playFromSource ? song : song.stream.song;
1509
+ const stream = willPlaySong?.stream;
1510
+ if (!willPlaySong || !stream?.playFromSource || !stream.url) throw new DisTubeError("NO_STREAM_URL", `${song}`);
1511
+ this.debug(`[${queue.id}] Creating DisTubeStream for: ${willPlaySong}`);
1512
+ const streamOptions = {
1513
+ ffmpeg: {
1514
+ path: this.options.ffmpeg.path,
1515
+ args: {
1516
+ global: { ...queue.ffmpegArgs.global },
1517
+ input: { ...queue.ffmpegArgs.input },
1518
+ output: { ...queue.ffmpegArgs.output, ...queue.filters.ffmpegArgs }
1519
+ }
1520
+ },
1521
+ seek: willPlaySong.duration ? queue._beginTime : void 0
1522
+ };
1523
+ const dtStream = new DisTubeStream(stream.url, streamOptions);
1524
+ dtStream.on("debug", (data) => this.emit("ffmpegDebug" /* FFMPEG_DEBUG */, `[${queue.id}] ${data}`));
1525
+ this.debug(`[${queue.id}] Started playing: ${willPlaySong}`);
1526
+ queue.voice.play(dtStream);
1527
+ if (emitPlaySong) this.emit("playSong" /* PLAY_SONG */, queue, song);
2050
1528
  } catch (e) {
2051
- __privateMethod(this, _handlePlayingError, handlePlayingError_fn).call(this, queue, e);
2052
- return true;
1529
+ __privateMethod(this, _QueueManager_instances, handlePlayingError_fn).call(this, queue, e);
2053
1530
  }
2054
1531
  }
2055
1532
  };
2056
- _voiceEventHandler = new WeakSet();
1533
+ _QueueManager_instances = new WeakSet();
1534
+ /**
1535
+ * Listen to DisTubeVoice events and handle the Queue
1536
+ * @param queue - Queue
1537
+ */
2057
1538
  voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
2058
1539
  queue._listeners = {
2059
- disconnect: (error) => {
1540
+ disconnect: /* @__PURE__ */ __name((error) => {
2060
1541
  queue.remove();
2061
1542
  this.emit("disconnect" /* DISCONNECT */, queue);
2062
- if (error)
2063
- this.emitError(error, queue.textChannel);
2064
- },
2065
- error: (error) => __privateMethod(this, _handlePlayingError, handlePlayingError_fn).call(this, queue, error),
2066
- finish: () => __privateMethod(this, _handleSongFinish, handleSongFinish_fn).call(this, queue)
1543
+ if (error) this.emitError(error, queue, queue.songs?.[0]);
1544
+ }, "disconnect"),
1545
+ error: /* @__PURE__ */ __name((error) => __privateMethod(this, _QueueManager_instances, handlePlayingError_fn).call(this, queue, error), "error"),
1546
+ finish: /* @__PURE__ */ __name(() => __privateMethod(this, _QueueManager_instances, handleSongFinish_fn).call(this, queue), "finish")
2067
1547
  };
2068
1548
  for (const event of objectKeys(queue._listeners)) {
2069
1549
  queue.voice.on(event, queue._listeners[event]);
2070
1550
  }
2071
1551
  }, "#voiceEventHandler");
2072
- _emitPlaySong = new WeakSet();
1552
+ /**
1553
+ * Whether or not emit playSong event
1554
+ * @param queue - Queue
1555
+ */
2073
1556
  emitPlaySong_fn = /* @__PURE__ */ __name(function(queue) {
2074
1557
  return !this.options.emitNewSongOnly || queue.repeatMode === 1 /* SONG */ && queue._next || queue.repeatMode !== 1 /* SONG */ && queue.songs[0]?.id !== queue.songs[1]?.id;
2075
1558
  }, "#emitPlaySong");
2076
- _handleSongFinish = new WeakSet();
2077
1559
  handleSongFinish_fn = /* @__PURE__ */ __name(async function(queue) {
1560
+ this.debug(`[QueueManager] Handling song finish: ${queue.id}`);
1561
+ const song = queue.songs[0];
2078
1562
  this.emit("finishSong" /* FINISH_SONG */, queue, queue.songs[0]);
2079
1563
  await queue._taskQueue.queuing();
2080
1564
  try {
2081
- if (queue.stopped)
2082
- return;
2083
- if (queue.repeatMode === 2 /* QUEUE */ && !queue._prev)
2084
- queue.songs.push(queue.songs[0]);
1565
+ if (queue.stopped) return;
1566
+ if (queue.repeatMode === 2 /* QUEUE */ && !queue._prev) queue.songs.push(song);
2085
1567
  if (queue._prev) {
2086
- if (queue.repeatMode === 2 /* QUEUE */)
2087
- queue.songs.unshift(queue.songs.pop());
2088
- else
2089
- queue.songs.unshift(queue.previousSongs.pop());
1568
+ if (queue.repeatMode === 2 /* QUEUE */) queue.songs.unshift(queue.songs.pop());
1569
+ else queue.songs.unshift(queue.previousSongs.pop());
2090
1570
  }
2091
1571
  if (queue.songs.length <= 1 && (queue._next || queue.repeatMode === 0 /* DISABLED */)) {
2092
1572
  if (queue.autoplay) {
2093
1573
  try {
1574
+ this.debug(`[QueueManager] Adding related song: ${queue.id}`);
2094
1575
  await queue.addRelatedSong();
2095
- } catch {
2096
- this.emit("noRelated" /* NO_RELATED */, queue);
1576
+ } catch (e) {
1577
+ this.debug(`[${queue.id}] Add related song error: ${e.message}`);
1578
+ this.emit("noRelated" /* NO_RELATED */, queue, e);
2097
1579
  }
2098
1580
  }
2099
1581
  if (queue.songs.length <= 1) {
2100
- if (this.options.leaveOnFinish)
2101
- queue.voice.leave();
2102
- if (!queue.autoplay)
2103
- this.emit("finish" /* FINISH */, queue);
1582
+ this.debug(`[${queue.id}] Queue is empty, stopping...`);
1583
+ if (!queue.autoplay) this.emit("finish" /* FINISH */, queue);
2104
1584
  queue.remove();
2105
1585
  return;
2106
1586
  }
2107
1587
  }
2108
- const emitPlaySong = __privateMethod(this, _emitPlaySong, emitPlaySong_fn).call(this, queue);
1588
+ const emitPlaySong = __privateMethod(this, _QueueManager_instances, emitPlaySong_fn).call(this, queue);
2109
1589
  if (!queue._prev && (queue.repeatMode !== 1 /* SONG */ || queue._next)) {
2110
1590
  const prev = queue.songs.shift();
2111
- delete prev.formats;
2112
- delete prev.streamURL;
2113
- if (this.options.savePreviousSongs)
2114
- queue.previousSongs.push(prev);
2115
- else
2116
- queue.previousSongs.push({ id: prev.id });
1591
+ if (this.options.savePreviousSongs) queue.previousSongs.push(prev);
1592
+ else queue.previousSongs.push({ id: prev.id });
2117
1593
  }
2118
1594
  queue._next = queue._prev = false;
2119
- queue.beginTime = 0;
2120
- const err = await this.playSong(queue);
2121
- if (!err && emitPlaySong)
2122
- this.emit("playSong" /* PLAY_SONG */, queue, queue.songs[0]);
1595
+ queue._beginTime = 0;
1596
+ if (song !== queue.songs[0]) {
1597
+ const playedSong = song.stream.playFromSource ? song : song.stream.song;
1598
+ if (playedSong?.stream.playFromSource) delete playedSong.stream.url;
1599
+ }
1600
+ await this.playSong(queue, emitPlaySong);
2123
1601
  } finally {
2124
1602
  queue._taskQueue.resolve();
2125
1603
  }
2126
1604
  }, "#handleSongFinish");
2127
- _handlePlayingError = new WeakSet();
1605
+ /**
1606
+ * Handle error while playing
1607
+ * @param queue - queue
1608
+ * @param error - error
1609
+ */
2128
1610
  handlePlayingError_fn = /* @__PURE__ */ __name(function(queue, error) {
2129
1611
  const song = queue.songs.shift();
2130
1612
  try {
2131
1613
  error.name = "PlayingError";
2132
- error.message = `${error.message}
2133
- Id: ${song.id}
2134
- Name: ${song.name}`;
2135
1614
  } catch {
2136
1615
  }
2137
- this.emitError(error, queue.textChannel);
1616
+ this.debug(`[${queue.id}] Error while playing: ${error.stack || error.message}`);
1617
+ this.emitError(error, queue, song);
2138
1618
  if (queue.songs.length > 0) {
1619
+ this.debug(`[${queue.id}] Playing next song: ${queue.songs[0]}`);
2139
1620
  queue._next = queue._prev = false;
2140
- queue.beginTime = 0;
2141
- this.playSong(queue).then((e) => {
2142
- if (!e)
2143
- this.emit("playSong" /* PLAY_SONG */, queue, queue.songs[0]);
2144
- });
1621
+ queue._beginTime = 0;
1622
+ this.playSong(queue);
2145
1623
  } else {
1624
+ this.debug(`[${queue.id}] Queue is empty, stopping...`);
2146
1625
  queue.stop();
2147
1626
  }
2148
1627
  }, "#handlePlayingError");
@@ -2150,53 +1629,106 @@ __name(_QueueManager, "QueueManager");
2150
1629
  var QueueManager = _QueueManager;
2151
1630
 
2152
1631
  // src/struct/Queue.ts
2153
- var _filters;
1632
+ var _filters, _Queue_instances, getRelatedSong_fn;
2154
1633
  var _Queue = class _Queue extends DisTubeBase {
2155
1634
  /**
2156
1635
  * Create a queue for the guild
2157
- *
2158
1636
  * @param distube - DisTube
2159
1637
  * @param voice - Voice connection
2160
- * @param song - First song(s)
2161
1638
  * @param textChannel - Default text channel
2162
1639
  */
2163
- constructor(distube, voice, song, textChannel) {
1640
+ constructor(distube, voice, textChannel) {
2164
1641
  super(distube);
1642
+ __privateAdd(this, _Queue_instances);
1643
+ /**
1644
+ * Queue id (Guild id)
1645
+ */
2165
1646
  __publicField(this, "id");
1647
+ /**
1648
+ * Voice connection of this queue.
1649
+ */
2166
1650
  __publicField(this, "voice");
1651
+ /**
1652
+ * List of songs in the queue (The first one is the playing song)
1653
+ */
2167
1654
  __publicField(this, "songs");
1655
+ /**
1656
+ * List of the previous songs.
1657
+ */
2168
1658
  __publicField(this, "previousSongs");
1659
+ /**
1660
+ * Whether stream is currently stopped.
1661
+ */
2169
1662
  __publicField(this, "stopped");
2170
- __publicField(this, "_next");
2171
- __publicField(this, "_prev");
1663
+ /**
1664
+ * Whether or not the stream is currently playing.
1665
+ */
2172
1666
  __publicField(this, "playing");
1667
+ /**
1668
+ * Whether or not the stream is currently paused.
1669
+ */
2173
1670
  __publicField(this, "paused");
1671
+ /**
1672
+ * Type of repeat mode (`0` is disabled, `1` is repeating a song, `2` is repeating
1673
+ * all the queue). Default value: `0` (disabled)
1674
+ */
2174
1675
  __publicField(this, "repeatMode");
1676
+ /**
1677
+ * Whether or not the autoplay mode is enabled. Default value: `false`
1678
+ */
2175
1679
  __publicField(this, "autoplay");
2176
- __privateAdd(this, _filters, void 0);
2177
- __publicField(this, "beginTime");
1680
+ /**
1681
+ * FFmpeg arguments for the current queue. Default value is defined with {@link DisTubeOptions}.ffmpeg.args.
1682
+ * `af` output argument will be replaced with {@link Queue#filters} manager
1683
+ */
1684
+ __publicField(this, "ffmpegArgs");
1685
+ /**
1686
+ * The text channel of the Queue. (Default: where the first command is called).
1687
+ */
2178
1688
  __publicField(this, "textChannel");
2179
- __publicField(this, "_emptyTimeout");
1689
+ __privateAdd(this, _filters);
1690
+ /**
1691
+ * What time in the song to begin (in seconds).
1692
+ */
1693
+ __publicField(this, "_beginTime");
1694
+ /**
1695
+ * Whether or not the last song was skipped to next song.
1696
+ */
1697
+ __publicField(this, "_next");
1698
+ /**
1699
+ * Whether or not the last song was skipped to previous song.
1700
+ */
1701
+ __publicField(this, "_prev");
1702
+ /**
1703
+ * Task queuing system
1704
+ */
2180
1705
  __publicField(this, "_taskQueue");
1706
+ /**
1707
+ * {@link DisTubeVoice} listener
1708
+ */
2181
1709
  __publicField(this, "_listeners");
2182
1710
  this.voice = voice;
2183
1711
  this.id = voice.id;
2184
1712
  this.volume = 50;
2185
- this.songs = Array.isArray(song) ? [...song] : [song];
1713
+ this.songs = [];
2186
1714
  this.previousSongs = [];
2187
1715
  this.stopped = false;
2188
1716
  this._next = false;
2189
1717
  this._prev = false;
2190
- this.playing = true;
1718
+ this.playing = false;
2191
1719
  this.paused = false;
2192
1720
  this.repeatMode = 0 /* DISABLED */;
2193
1721
  this.autoplay = false;
2194
1722
  __privateSet(this, _filters, new FilterManager(this));
2195
- this.beginTime = 0;
1723
+ this._beginTime = 0;
2196
1724
  this.textChannel = textChannel;
2197
- this._emptyTimeout = void 0;
2198
1725
  this._taskQueue = new TaskQueue();
2199
1726
  this._listeners = void 0;
1727
+ this.ffmpegArgs = {
1728
+ global: { ...this.options.ffmpeg.args.global },
1729
+ input: { ...this.options.ffmpeg.args.input },
1730
+ output: { ...this.options.ffmpeg.args.output }
1731
+ };
2200
1732
  }
2201
1733
  /**
2202
1734
  * The client user as a `GuildMember` of this queue's guild
@@ -2226,7 +1758,7 @@ var _Queue = class _Queue extends DisTubeBase {
2226
1758
  * What time in the song is playing (in seconds).
2227
1759
  */
2228
1760
  get currentTime() {
2229
- return this.voice.playbackDuration + this.beginTime;
1761
+ return this.voice.playbackDuration + this._beginTime;
2230
1762
  }
2231
1763
  /**
2232
1764
  * Formatted {@link Queue#currentTime} string.
@@ -2240,6 +1772,9 @@ var _Queue = class _Queue extends DisTubeBase {
2240
1772
  get voiceChannel() {
2241
1773
  return this.clientMember?.voice?.channel ?? null;
2242
1774
  }
1775
+ /**
1776
+ * Get or set the stream volume. Default value: `50`.
1777
+ */
2243
1778
  get volume() {
2244
1779
  return this.voice.volume;
2245
1780
  }
@@ -2248,13 +1783,12 @@ var _Queue = class _Queue extends DisTubeBase {
2248
1783
  }
2249
1784
  /**
2250
1785
  * @throws {DisTubeError}
2251
- *
2252
1786
  * @param song - Song to add
2253
1787
  * @param position - Position to add, \<= 0 to add to the end of the queue
2254
- *
2255
1788
  * @returns The guild queue
2256
1789
  */
2257
1790
  addToQueue(song, position = 0) {
1791
+ if (this.stopped) throw new DisTubeError("QUEUE_STOPPED");
2258
1792
  if (!song || Array.isArray(song) && !song.length) {
2259
1793
  throw new DisTubeError("INVALID_TYPE", ["Song", "Array<Song>"], song, "song");
2260
1794
  }
@@ -2262,52 +1796,38 @@ var _Queue = class _Queue extends DisTubeBase {
2262
1796
  throw new DisTubeError("INVALID_TYPE", "integer", position, "position");
2263
1797
  }
2264
1798
  if (position <= 0) {
2265
- if (Array.isArray(song))
2266
- this.songs.push(...song);
2267
- else
2268
- this.songs.push(song);
1799
+ if (Array.isArray(song)) this.songs.push(...song);
1800
+ else this.songs.push(song);
2269
1801
  } else if (Array.isArray(song)) {
2270
1802
  this.songs.splice(position, 0, ...song);
2271
1803
  } else {
2272
1804
  this.songs.splice(position, 0, song);
2273
1805
  }
2274
- if (Array.isArray(song))
2275
- song.forEach((s) => delete s.formats);
2276
- else
2277
- delete song.formats;
2278
1806
  return this;
2279
1807
  }
2280
1808
  /**
2281
1809
  * Pause the guild stream
2282
- *
2283
1810
  * @returns The guild queue
2284
1811
  */
2285
1812
  pause() {
2286
- if (this.paused)
2287
- throw new DisTubeError("PAUSED");
2288
- this.playing = false;
1813
+ if (this.paused) throw new DisTubeError("PAUSED");
2289
1814
  this.paused = true;
2290
1815
  this.voice.pause();
2291
1816
  return this;
2292
1817
  }
2293
1818
  /**
2294
1819
  * Resume the guild stream
2295
- *
2296
1820
  * @returns The guild queue
2297
1821
  */
2298
1822
  resume() {
2299
- if (this.playing)
2300
- throw new DisTubeError("RESUMED");
2301
- this.playing = true;
1823
+ if (!this.paused) throw new DisTubeError("RESUMED");
2302
1824
  this.paused = false;
2303
1825
  this.voice.unpause();
2304
1826
  return this;
2305
1827
  }
2306
1828
  /**
2307
1829
  * Set the guild stream's volume
2308
- *
2309
1830
  * @param percent - The percentage of volume you want to set
2310
- *
2311
1831
  * @returns The guild queue
2312
1832
  */
2313
1833
  setVolume(percent) {
@@ -2318,17 +1838,14 @@ var _Queue = class _Queue extends DisTubeBase {
2318
1838
  * Skip the playing song if there is a next song in the queue. <info>If {@link
2319
1839
  * Queue#autoplay} is `true` and there is no up next song, DisTube will add and
2320
1840
  * play a related song.</info>
2321
- *
2322
1841
  * @returns The song will skip to
2323
1842
  */
2324
1843
  async skip() {
2325
1844
  await this._taskQueue.queuing();
2326
1845
  try {
2327
1846
  if (this.songs.length <= 1) {
2328
- if (this.autoplay)
2329
- await this.addRelatedSong();
2330
- else
2331
- throw new DisTubeError("NO_UP_NEXT");
1847
+ if (this.autoplay) await this.addRelatedSong();
1848
+ else throw new DisTubeError("NO_UP_NEXT");
2332
1849
  }
2333
1850
  const song = this.songs[1];
2334
1851
  this._next = true;
@@ -2340,14 +1857,12 @@ var _Queue = class _Queue extends DisTubeBase {
2340
1857
  }
2341
1858
  /**
2342
1859
  * Play the previous song if exists
2343
- *
2344
1860
  * @returns The guild queue
2345
1861
  */
2346
1862
  async previous() {
2347
1863
  await this._taskQueue.queuing();
2348
1864
  try {
2349
- if (!this.options.savePreviousSongs)
2350
- throw new DisTubeError("DISABLED_OPTION", "savePreviousSongs");
1865
+ if (!this.options.savePreviousSongs) throw new DisTubeError("DISABLED_OPTION", "savePreviousSongs");
2351
1866
  if (this.previousSongs?.length === 0 && this.repeatMode !== 2 /* QUEUE */) {
2352
1867
  throw new DisTubeError("NO_PREVIOUS");
2353
1868
  }
@@ -2361,15 +1876,13 @@ var _Queue = class _Queue extends DisTubeBase {
2361
1876
  }
2362
1877
  /**
2363
1878
  * Shuffle the queue's songs
2364
- *
2365
1879
  * @returns The guild queue
2366
1880
  */
2367
1881
  async shuffle() {
2368
1882
  await this._taskQueue.queuing();
2369
1883
  try {
2370
1884
  const playing = this.songs.shift();
2371
- if (playing === void 0)
2372
- return this;
1885
+ if (playing === void 0) return this;
2373
1886
  for (let i = this.songs.length - 1; i > 0; i--) {
2374
1887
  const j = Math.floor(Math.random() * (i + 1));
2375
1888
  [this.songs[i], this.songs[j]] = [this.songs[j], this.songs[i]];
@@ -2384,16 +1897,13 @@ var _Queue = class _Queue extends DisTubeBase {
2384
1897
  * Jump to the song position in the queue. The next one is 1, 2,... The previous
2385
1898
  * one is -1, -2,...
2386
1899
  * if `num` is invalid number
2387
- *
2388
1900
  * @param position - The song position to play
2389
- *
2390
1901
  * @returns The new Song will be played
2391
1902
  */
2392
1903
  async jump(position) {
2393
1904
  await this._taskQueue.queuing();
2394
1905
  try {
2395
- if (typeof position !== "number")
2396
- throw new DisTubeError("INVALID_TYPE", "number", position, "position");
1906
+ if (typeof position !== "number") throw new DisTubeError("INVALID_TYPE", "number", position, "position");
2397
1907
  if (!position || position > this.songs.length || -position > this.previousSongs.length) {
2398
1908
  throw new DisTubeError("NO_SONG_POSITION");
2399
1909
  }
@@ -2412,8 +1922,7 @@ var _Queue = class _Queue extends DisTubeBase {
2412
1922
  throw new DisTubeError("DISABLED_OPTION", "savePreviousSongs");
2413
1923
  } else {
2414
1924
  this._prev = true;
2415
- if (position !== -1)
2416
- this.songs.unshift(...this.previousSongs.splice(position + 1));
1925
+ if (position !== -1) this.songs.unshift(...this.previousSongs.splice(position + 1));
2417
1926
  nextSong = this.previousSongs[this.previousSongs.length - 1];
2418
1927
  }
2419
1928
  this.voice.stop();
@@ -2425,53 +1934,49 @@ var _Queue = class _Queue extends DisTubeBase {
2425
1934
  /**
2426
1935
  * Set the repeat mode of the guild queue.
2427
1936
  * Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
2428
- *
2429
1937
  * @param mode - The repeat modes (toggle if `undefined`)
2430
- *
2431
1938
  * @returns The new repeat mode
2432
1939
  */
2433
1940
  setRepeatMode(mode) {
2434
1941
  if (mode !== void 0 && !Object.values(RepeatMode).includes(mode)) {
2435
1942
  throw new DisTubeError("INVALID_TYPE", ["RepeatMode", "undefined"], mode, "mode");
2436
1943
  }
2437
- if (mode === void 0)
2438
- this.repeatMode = (this.repeatMode + 1) % 3;
2439
- else if (this.repeatMode === mode)
2440
- this.repeatMode = 0 /* DISABLED */;
2441
- else
2442
- this.repeatMode = mode;
1944
+ if (mode === void 0) this.repeatMode = (this.repeatMode + 1) % 3;
1945
+ else if (this.repeatMode === mode) this.repeatMode = 0 /* DISABLED */;
1946
+ else this.repeatMode = mode;
2443
1947
  return this.repeatMode;
2444
1948
  }
2445
1949
  /**
2446
1950
  * Set the playing time to another position
2447
- *
2448
1951
  * @param time - Time in seconds
2449
- *
2450
1952
  * @returns The guild queue
2451
1953
  */
2452
1954
  seek(time) {
2453
- if (typeof time !== "number")
2454
- throw new DisTubeError("INVALID_TYPE", "number", time, "time");
2455
- if (isNaN(time) || time < 0)
2456
- throw new DisTubeError("NUMBER_COMPARE", "time", "bigger or equal to", 0);
2457
- this.beginTime = time;
2458
- this.queues.playSong(this);
1955
+ if (typeof time !== "number") throw new DisTubeError("INVALID_TYPE", "number", time, "time");
1956
+ if (isNaN(time) || time < 0) throw new DisTubeError("NUMBER_COMPARE", "time", "bigger or equal to", 0);
1957
+ this._beginTime = time;
1958
+ this.play(false);
2459
1959
  return this;
2460
1960
  }
2461
1961
  /**
2462
1962
  * Add a related song of the playing song to the queue
2463
- *
2464
1963
  * @returns The added song
2465
1964
  */
2466
1965
  async addRelatedSong() {
2467
- if (!this.songs?.[0])
2468
- throw new DisTubeError("NO_PLAYING");
2469
- const related = this.songs[0].related.find((v) => !this.previousSongs.map((s) => s.id).includes(v.id));
2470
- if (!related || !(related instanceof Song))
2471
- throw new DisTubeError("NO_RELATED");
2472
- const song = await this.handler.resolve(related, { member: this.clientMember, metadata: related.metadata });
2473
- if (!(song instanceof Song))
2474
- throw new DisTubeError("CANNOT_PLAY_RELATED");
1966
+ const current = this.songs?.[0];
1967
+ if (!current) throw new DisTubeError("NO_PLAYING_SONG");
1968
+ const prevIds = this.previousSongs.map((p) => p.id);
1969
+ const relatedSongs = (await __privateMethod(this, _Queue_instances, getRelatedSong_fn).call(this, current)).filter((s) => !prevIds.includes(s.id));
1970
+ this.debug(`[${this.id}] Getting related songs from: ${current}`);
1971
+ if (!relatedSongs.length && !current.stream.playFromSource) {
1972
+ const altSong = current.stream.song;
1973
+ if (altSong) relatedSongs.push(...(await __privateMethod(this, _Queue_instances, getRelatedSong_fn).call(this, altSong)).filter((s) => !prevIds.includes(s.id)));
1974
+ this.debug(`[${this.id}] Getting related songs from streamed song: ${altSong}`);
1975
+ }
1976
+ const song = relatedSongs[0];
1977
+ if (!song) throw new DisTubeError("NO_RELATED");
1978
+ song.metadata = current.metadata;
1979
+ song.member = this.clientMember;
2475
1980
  this.addToQueue(song);
2476
1981
  return song;
2477
1982
  }
@@ -2484,18 +1989,14 @@ var _Queue = class _Queue extends DisTubeBase {
2484
1989
  this.playing = false;
2485
1990
  this.paused = false;
2486
1991
  this.stopped = true;
2487
- if (this.options.leaveOnStop)
2488
- this.voice.leave();
2489
- else
2490
- this.voice.stop();
1992
+ this.voice.stop();
2491
1993
  this.remove();
2492
1994
  } finally {
2493
1995
  this._taskQueue.resolve();
2494
1996
  }
2495
1997
  }
2496
1998
  /**
2497
- * Remove the queue from the manager (This does not leave the voice channel even if
2498
- * {@link DisTubeOptions | DisTubeOptions.leaveOnStop} is enabled)
1999
+ * Remove the queue from the manager
2499
2000
  */
2500
2001
  remove() {
2501
2002
  this.stopped = true;
@@ -2503,7 +2004,7 @@ var _Queue = class _Queue extends DisTubeBase {
2503
2004
  this.previousSongs = [];
2504
2005
  if (this._listeners) {
2505
2006
  for (const event of objectKeys(this._listeners)) {
2506
- this.voice.removeListener(event, this._listeners[event]);
2007
+ this.voice.off(event, this._listeners[event]);
2507
2008
  }
2508
2009
  }
2509
2010
  this.queues.remove(this.id);
@@ -2511,118 +2012,47 @@ var _Queue = class _Queue extends DisTubeBase {
2511
2012
  }
2512
2013
  /**
2513
2014
  * Toggle autoplay mode
2514
- *
2515
2015
  * @returns Autoplay mode state
2516
2016
  */
2517
2017
  toggleAutoplay() {
2518
2018
  this.autoplay = !this.autoplay;
2519
2019
  return this.autoplay;
2520
2020
  }
2021
+ /**
2022
+ * Play the queue
2023
+ * @param emitPlaySong - Whether or not emit {@link Events.PLAY_SONG} event
2024
+ */
2025
+ play(emitPlaySong = true) {
2026
+ if (this.stopped) throw new DisTubeError("QUEUE_STOPPED");
2027
+ this.playing = true;
2028
+ return this.queues.playSong(this, emitPlaySong);
2029
+ }
2521
2030
  };
2522
2031
  _filters = new WeakMap();
2032
+ _Queue_instances = new WeakSet();
2033
+ getRelatedSong_fn = /* @__PURE__ */ __name(async function(current) {
2034
+ const plugin = await this.handler._getPluginFromSong(current);
2035
+ if (plugin) return plugin.getRelatedSongs(current);
2036
+ return [];
2037
+ }, "#getRelatedSong");
2523
2038
  __name(_Queue, "Queue");
2524
2039
  var Queue = _Queue;
2525
2040
 
2526
2041
  // src/struct/Plugin.ts
2527
2042
  var _Plugin = class _Plugin {
2528
2043
  constructor() {
2044
+ /**
2045
+ * DisTube
2046
+ */
2529
2047
  __publicField(this, "distube");
2530
2048
  }
2531
2049
  init(distube) {
2532
2050
  this.distube = distube;
2533
2051
  }
2534
- /**
2535
- * Type of the plugin
2536
- */
2537
- /**
2538
- * Emit an event to the {@link DisTube} class
2539
- *
2540
- * @param eventName - Event name
2541
- * @param args - arguments
2542
- */
2543
- emit(eventName, ...args) {
2544
- return this.distube.emit(eventName, ...args);
2545
- }
2546
- /**
2547
- * Emit error event to the {@link DisTube} class
2548
- *
2549
- * @param error - error
2550
- * @param channel - Text channel where the error is encountered.
2551
- */
2552
- emitError(error, channel) {
2553
- this.distube.emitError(error, channel);
2554
- }
2555
- /**
2556
- * The queue manager
2557
- */
2558
- get queues() {
2559
- return this.distube.queues;
2560
- }
2561
- /**
2562
- * The voice manager
2563
- */
2564
- get voices() {
2565
- return this.distube.voices;
2566
- }
2567
- /**
2568
- * Discord.js client
2569
- */
2570
- get client() {
2571
- return this.distube.client;
2572
- }
2573
- /**
2574
- * DisTube options
2575
- */
2576
- get options() {
2577
- return this.distube.options;
2578
- }
2579
- /**
2580
- * DisTube handler
2581
- */
2582
- get handler() {
2583
- return this.distube.handler;
2584
- }
2585
- /**
2586
- * Check if the string is working with this plugin
2587
- *
2588
- * @param _string - Input string
2589
- */
2590
- validate(_string) {
2591
- return false;
2592
- }
2593
- /**
2594
- * Get the stream url from {@link Song#url}. Returns {@link Song#url} by default.
2595
- * Not needed if the plugin plays song from YouTube.
2596
- *
2597
- * @param url - Input url
2598
- */
2599
- getStreamURL(url) {
2600
- return url;
2601
- }
2602
- /**
2603
- * Get related songs from a supported url. {@link Song#member} should be
2604
- * `undefined`. Not needed to add {@link Song#related} because it will be added
2605
- * with this function later.
2606
- *
2607
- * @param _url - Input url
2608
- */
2609
- getRelatedSongs(_url) {
2610
- return [];
2611
- }
2612
2052
  };
2613
2053
  __name(_Plugin, "Plugin");
2614
2054
  var Plugin = _Plugin;
2615
2055
 
2616
- // src/struct/CustomPlugin.ts
2617
- var _CustomPlugin = class _CustomPlugin extends Plugin {
2618
- constructor() {
2619
- super(...arguments);
2620
- __publicField(this, "type", "custom" /* CUSTOM */);
2621
- }
2622
- };
2623
- __name(_CustomPlugin, "CustomPlugin");
2624
- var CustomPlugin = _CustomPlugin;
2625
-
2626
2056
  // src/struct/ExtractorPlugin.ts
2627
2057
  var _ExtractorPlugin = class _ExtractorPlugin extends Plugin {
2628
2058
  constructor() {
@@ -2633,56 +2063,46 @@ var _ExtractorPlugin = class _ExtractorPlugin extends Plugin {
2633
2063
  __name(_ExtractorPlugin, "ExtractorPlugin");
2634
2064
  var ExtractorPlugin = _ExtractorPlugin;
2635
2065
 
2066
+ // src/struct/InfoExtratorPlugin.ts
2067
+ var _InfoExtractorPlugin = class _InfoExtractorPlugin extends Plugin {
2068
+ constructor() {
2069
+ super(...arguments);
2070
+ __publicField(this, "type", "info-extractor" /* INFO_EXTRACTOR */);
2071
+ }
2072
+ };
2073
+ __name(_InfoExtractorPlugin, "InfoExtractorPlugin");
2074
+ var InfoExtractorPlugin = _InfoExtractorPlugin;
2075
+
2076
+ // src/struct/PlayableExtratorPlugin.ts
2077
+ var _PlayableExtractorPlugin = class _PlayableExtractorPlugin extends Plugin {
2078
+ constructor() {
2079
+ super(...arguments);
2080
+ __publicField(this, "type", "playable-extractor" /* PLAYABLE_EXTRACTOR */);
2081
+ }
2082
+ };
2083
+ __name(_PlayableExtractorPlugin, "PlayableExtractorPlugin");
2084
+ var PlayableExtractorPlugin = _PlayableExtractorPlugin;
2085
+
2636
2086
  // src/util.ts
2637
2087
  var import_url = require("url");
2638
2088
  var import_discord3 = require("discord.js");
2639
2089
  var formatInt = /* @__PURE__ */ __name((int) => int < 10 ? `0${int}` : int, "formatInt");
2640
2090
  function formatDuration(sec) {
2641
- if (!sec || !Number(sec))
2642
- return "00:00";
2091
+ if (!sec || !Number(sec)) return "00:00";
2643
2092
  const seconds = Math.floor(sec % 60);
2644
2093
  const minutes = Math.floor(sec % 3600 / 60);
2645
2094
  const hours = Math.floor(sec / 3600);
2646
- if (hours > 0)
2647
- return `${formatInt(hours)}:${formatInt(minutes)}:${formatInt(seconds)}`;
2648
- if (minutes > 0)
2649
- return `${formatInt(minutes)}:${formatInt(seconds)}`;
2095
+ if (hours > 0) return `${formatInt(hours)}:${formatInt(minutes)}:${formatInt(seconds)}`;
2096
+ if (minutes > 0) return `${formatInt(minutes)}:${formatInt(seconds)}`;
2650
2097
  return `00:${formatInt(seconds)}`;
2651
2098
  }
2652
2099
  __name(formatDuration, "formatDuration");
2653
- function toSecond(input) {
2654
- if (!input)
2655
- return 0;
2656
- if (typeof input !== "string")
2657
- return Number(input) || 0;
2658
- if (input.includes(":")) {
2659
- const time = input.split(":").reverse();
2660
- let seconds = 0;
2661
- for (let i = 0; i < 3; i++)
2662
- if (time[i])
2663
- seconds += Number(time[i].replace(/[^\d.]+/g, "")) * Math.pow(60, i);
2664
- if (time.length > 3)
2665
- seconds += Number(time[3].replace(/[^\d.]+/g, "")) * 24 * 60 * 60;
2666
- return seconds;
2667
- } else {
2668
- return Number(input.replace(/[^\d.]+/g, "")) || 0;
2669
- }
2670
- }
2671
- __name(toSecond, "toSecond");
2672
- function parseNumber(input) {
2673
- if (typeof input === "string")
2674
- return Number(input.replace(/[^\d.]+/g, "")) || 0;
2675
- return Number(input) || 0;
2676
- }
2677
- __name(parseNumber, "parseNumber");
2678
2100
  var SUPPORTED_PROTOCOL = ["https:", "http:", "file:"];
2679
2101
  function isURL(input) {
2680
- if (typeof input !== "string" || input.includes(" "))
2681
- return false;
2102
+ if (typeof input !== "string" || input.includes(" ")) return false;
2682
2103
  try {
2683
2104
  const url = new import_url.URL(input);
2684
- if (!SUPPORTED_PROTOCOL.some((p) => p === url.protocol))
2685
- return false;
2105
+ if (!SUPPORTED_PROTOCOL.some((p) => p === url.protocol)) return false;
2686
2106
  } catch {
2687
2107
  return false;
2688
2108
  }
@@ -2690,19 +2110,16 @@ function isURL(input) {
2690
2110
  }
2691
2111
  __name(isURL, "isURL");
2692
2112
  function checkIntents(options) {
2693
- const intents = new import_discord3.IntentsBitField(options.intents);
2694
- if (!intents.has(import_discord3.GatewayIntentBits.GuildVoiceStates))
2695
- throw new DisTubeError("MISSING_INTENTS", "GuildVoiceStates");
2113
+ const intents = options.intents instanceof import_discord3.IntentsBitField ? options.intents : new import_discord3.IntentsBitField(options.intents);
2114
+ if (!intents.has(import_discord3.GatewayIntentBits.GuildVoiceStates)) throw new DisTubeError("MISSING_INTENTS", "GuildVoiceStates");
2696
2115
  }
2697
2116
  __name(checkIntents, "checkIntents");
2698
2117
  function isVoiceChannelEmpty(voiceState) {
2699
2118
  const guild = voiceState.guild;
2700
2119
  const clientId = voiceState.client.user?.id;
2701
- if (!guild || !clientId)
2702
- return false;
2120
+ if (!guild || !clientId) return false;
2703
2121
  const voiceChannel = guild.members.me?.voice?.channel;
2704
- if (!voiceChannel)
2705
- return false;
2122
+ if (!voiceChannel) return false;
2706
2123
  const members = voiceChannel.members.filter((m) => !m.user.bot);
2707
2124
  return !members.size;
2708
2125
  }
@@ -2748,8 +2165,7 @@ function resolveGuildId(resolvable) {
2748
2165
  guildId = resolvable.guild.id;
2749
2166
  }
2750
2167
  }
2751
- if (!isSnowflake(guildId))
2752
- throw new DisTubeError("INVALID_TYPE", "GuildIdResolvable", resolvable);
2168
+ if (!isSnowflake(guildId)) throw new DisTubeError("INVALID_TYPE", "GuildIdResolvable", resolvable);
2753
2169
  return guildId;
2754
2170
  }
2755
2171
  __name(resolveGuildId, "resolveGuildId");
@@ -2758,81 +2174,73 @@ function isClientInstance(client) {
2758
2174
  }
2759
2175
  __name(isClientInstance, "isClientInstance");
2760
2176
  function checkInvalidKey(target, source, sourceName) {
2761
- if (!isObject(target))
2762
- throw new DisTubeError("INVALID_TYPE", "object", target, sourceName);
2177
+ if (!isObject(target)) throw new DisTubeError("INVALID_TYPE", "object", target, sourceName);
2763
2178
  const sourceKeys = Array.isArray(source) ? source : objectKeys(source);
2764
2179
  const invalidKey = objectKeys(target).find((key) => !sourceKeys.includes(key));
2765
- if (invalidKey)
2766
- throw new DisTubeError("INVALID_KEY", sourceName, invalidKey);
2180
+ if (invalidKey) throw new DisTubeError("INVALID_KEY", sourceName, invalidKey);
2767
2181
  }
2768
2182
  __name(checkInvalidKey, "checkInvalidKey");
2769
2183
  function isObject(obj) {
2770
2184
  return typeof obj === "object" && obj !== null && !Array.isArray(obj);
2771
2185
  }
2772
2186
  __name(isObject, "isObject");
2773
- function isRecord(obj) {
2774
- return isObject(obj);
2775
- }
2776
- __name(isRecord, "isRecord");
2777
2187
  function objectKeys(obj) {
2778
- if (!isObject(obj))
2779
- return [];
2188
+ if (!isObject(obj)) return [];
2780
2189
  return Object.keys(obj);
2781
2190
  }
2782
2191
  __name(objectKeys, "objectKeys");
2783
2192
  function isNsfwChannel(channel) {
2784
- if (!isTextChannelInstance(channel))
2785
- return false;
2786
- if (channel.isThread())
2787
- return channel.parent?.nsfw ?? false;
2193
+ if (!isTextChannelInstance(channel)) return false;
2194
+ if (channel.isThread()) return channel.parent?.nsfw ?? false;
2788
2195
  return channel.nsfw;
2789
2196
  }
2790
2197
  __name(isNsfwChannel, "isNsfwChannel");
2791
2198
  var isTruthy = /* @__PURE__ */ __name((x) => Boolean(x), "isTruthy");
2792
2199
 
2793
- // src/plugin/DirectLink.ts
2794
- var import_undici = require("undici");
2795
- var _DirectLinkPlugin = class _DirectLinkPlugin extends ExtractorPlugin {
2796
- async validate(url) {
2797
- try {
2798
- const headers = await (0, import_undici.request)(url, { method: "HEAD" }).then((res) => res.headers);
2799
- const types = headers["content-type"];
2800
- const type = Array.isArray(types) ? types[0] : types;
2801
- if (["audio/", "video/", "application/ogg"].some((s) => type?.startsWith(s)))
2802
- return true;
2803
- } catch {
2804
- }
2805
- return false;
2806
- }
2807
- resolve(url, options = {}) {
2808
- const u = new URL(url);
2809
- const name = u.pathname.split("/").pop() || u.href;
2810
- return new Song({ name, url, src: "direct_link" }, options);
2811
- }
2812
- };
2813
- __name(_DirectLinkPlugin, "DirectLinkPlugin");
2814
- var DirectLinkPlugin = _DirectLinkPlugin;
2815
-
2816
2200
  // src/DisTube.ts
2817
- var import_ytsr = __toESM(require("@distube/ytsr"));
2818
2201
  var import_tiny_typed_emitter3 = require("tiny-typed-emitter");
2819
2202
  var { version } = require_package();
2820
- var _getQueue, getQueue_fn;
2821
- var _DisTube = class _DisTube extends import_tiny_typed_emitter3.TypedEmitter {
2203
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _DisTube_instances, getQueue_fn;
2204
+ var _DisTube = class _DisTube extends (_m = import_tiny_typed_emitter3.TypedEmitter, _l = "addList" /* ADD_LIST */, _k = "addSong" /* ADD_SONG */, _j = "deleteQueue" /* DELETE_QUEUE */, _i = "disconnect" /* DISCONNECT */, _h = "error" /* ERROR */, _g = "ffmpegDebug" /* FFMPEG_DEBUG */, _f = "debug" /* DEBUG */, _e = "finish" /* FINISH */, _d = "finishSong" /* FINISH_SONG */, _c = "initQueue" /* INIT_QUEUE */, _b = "noRelated" /* NO_RELATED */, _a = "playSong" /* PLAY_SONG */, _m) {
2205
+ /**
2206
+ * Create a new DisTube class.
2207
+ * @throws {@link DisTubeError}
2208
+ * @param client - Discord.JS client
2209
+ * @param opts - Custom DisTube options
2210
+ */
2822
2211
  constructor(client, opts = {}) {
2823
2212
  super();
2824
- __privateAdd(this, _getQueue);
2213
+ __privateAdd(this, _DisTube_instances);
2214
+ /**
2215
+ * DisTube internal handler
2216
+ */
2825
2217
  __publicField(this, "handler");
2218
+ /**
2219
+ * DisTube options
2220
+ */
2826
2221
  __publicField(this, "options");
2222
+ /**
2223
+ * Discord.js v14 client
2224
+ */
2827
2225
  __publicField(this, "client");
2226
+ /**
2227
+ * Queues manager
2228
+ */
2828
2229
  __publicField(this, "queues");
2230
+ /**
2231
+ * DisTube voice connections manager
2232
+ */
2829
2233
  __publicField(this, "voices");
2830
- __publicField(this, "extractorPlugins");
2831
- __publicField(this, "customPlugins");
2234
+ /**
2235
+ * DisTube plugins
2236
+ */
2237
+ __publicField(this, "plugins");
2238
+ /**
2239
+ * DisTube ffmpeg audio filters
2240
+ */
2832
2241
  __publicField(this, "filters");
2833
2242
  this.setMaxListeners(1);
2834
- if (!isClientInstance(client))
2835
- throw new DisTubeError("INVALID_TYPE", "Discord.Client", client, "client");
2243
+ if (!isClientInstance(client)) throw new DisTubeError("INVALID_TYPE", "Discord.Client", client, "client");
2836
2244
  this.client = client;
2837
2245
  checkIntents(client.options);
2838
2246
  this.options = new Options(opts);
@@ -2840,11 +2248,8 @@ var _DisTube = class _DisTube extends import_tiny_typed_emitter3.TypedEmitter {
2840
2248
  this.handler = new DisTubeHandler(this);
2841
2249
  this.queues = new QueueManager(this);
2842
2250
  this.filters = { ...defaultFilters, ...this.options.customFilters };
2843
- if (this.options.directLink)
2844
- this.options.plugins.push(new DirectLinkPlugin());
2845
- this.options.plugins.forEach((p) => p.init(this));
2846
- this.extractorPlugins = this.options.plugins.filter((p) => p.type === "extractor");
2847
- this.customPlugins = this.options.plugins.filter((p) => p.type === "custom");
2251
+ this.plugins = [...this.options.plugins];
2252
+ this.plugins.forEach((p) => p.init(this));
2848
2253
  }
2849
2254
  static get version() {
2850
2255
  return version;
@@ -2856,37 +2261,19 @@ var _DisTube = class _DisTube extends import_tiny_typed_emitter3.TypedEmitter {
2856
2261
  return version;
2857
2262
  }
2858
2263
  /**
2859
- * Play / add a song or playlist from url. Search and play a song if it is not a
2860
- * valid url.
2861
- *
2862
- * @example
2863
- * ```ts
2864
- * client.on('message', (message) => {
2865
- * if (!message.content.startsWith(config.prefix)) return;
2866
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
2867
- * const command = args.shift();
2868
- * if (command == "play")
2869
- * distube.play(message.member.voice.channel, args.join(" "), {
2870
- * member: message.member,
2871
- * textChannel: message.channel,
2872
- * message
2873
- * });
2874
- * });
2875
- * ```ts
2876
- *
2264
+ * Play / add a song or playlist from url.
2265
+ * Search and play a song (with {@link ExtractorPlugin}) if it is not a valid url.
2877
2266
  * @throws {@link DisTubeError}
2878
- *
2879
2267
  * @param voiceChannel - The channel will be joined if the bot isn't in any channels, the bot will be
2880
2268
  * moved to this channel if {@link DisTubeOptions}.joinNewVoiceChannel is `true`
2881
- * @param song - URL | Search string | {@link Song} | {@link SearchResult} | {@link Playlist}
2269
+ * @param song - URL | Search string | {@link Song} | {@link Playlist}
2882
2270
  * @param options - Optional options
2883
2271
  */
2884
2272
  async play(voiceChannel, song, options = {}) {
2885
2273
  if (!isSupportedVoiceChannel(voiceChannel)) {
2886
2274
  throw new DisTubeError("INVALID_TYPE", "BaseGuildVoiceChannel", voiceChannel, "voiceChannel");
2887
2275
  }
2888
- if (!isObject(options))
2889
- throw new DisTubeError("INVALID_TYPE", "object", options, "options");
2276
+ if (!isObject(options)) throw new DisTubeError("INVALID_TYPE", "object", options, "options");
2890
2277
  const { textChannel, member, skip, message, metadata } = {
2891
2278
  member: voiceChannel.guild.members.me ?? void 0,
2892
2279
  textChannel: options?.message?.channel,
@@ -2903,37 +2290,33 @@ var _DisTube = class _DisTube extends import_tiny_typed_emitter3.TypedEmitter {
2903
2290
  if (member && !isMemberInstance(member)) {
2904
2291
  throw new DisTubeError("INVALID_TYPE", "Discord.GuildMember", member, "options.member");
2905
2292
  }
2906
- const queue = this.getQueue(voiceChannel);
2907
- const queuing = queue && !queue._taskQueue.hasResolveTask;
2908
- if (queuing)
2909
- await queue?._taskQueue.queuing(true);
2293
+ const queue = this.getQueue(voiceChannel) || await this.queues.create(voiceChannel, textChannel);
2294
+ await queue._taskQueue.queuing();
2910
2295
  try {
2911
- if (typeof song === "string") {
2912
- for (const plugin of this.customPlugins) {
2913
- if (await plugin.validate(song)) {
2914
- await plugin.play(voiceChannel, song, options);
2915
- return;
2916
- }
2296
+ this.debug(`[${queue.id}] Resolving song: ${typeof song === "string" ? song : JSON.stringify(song)}`);
2297
+ const resolved = await this.handler.resolve(song, { member, metadata });
2298
+ const isNsfw = isNsfwChannel(queue?.textChannel || textChannel);
2299
+ if (resolved instanceof Playlist) {
2300
+ if (!this.options.nsfw && !isNsfw) {
2301
+ resolved.songs = resolved.songs.filter((s) => !s.ageRestricted);
2302
+ if (!resolved.songs.length) throw new DisTubeError("EMPTY_FILTERED_PLAYLIST");
2917
2303
  }
2918
- }
2919
- if (typeof song === "string" && !isURL(song)) {
2920
- if (!message) {
2921
- song = (await this.search(song, { limit: 1 }))[0];
2922
- } else {
2923
- const result = await this.handler.searchSong(message, song);
2924
- if (!result)
2925
- return;
2926
- song = result;
2927
- }
2928
- }
2929
- song = await this.handler.resolve(song, { member, metadata });
2930
- if (song instanceof Playlist) {
2931
- await this.handler.playPlaylist(voiceChannel, song, { textChannel, skip, position });
2304
+ if (!resolved.songs.length) throw new DisTubeError("EMPTY_PLAYLIST");
2305
+ this.debug(`[${queue.id}] Adding playlist to queue: ${resolved.songs.length} songs`);
2306
+ queue.addToQueue(resolved.songs, position);
2307
+ if (queue.playing || this.options.emitAddListWhenCreatingQueue) this.emit("addList" /* ADD_LIST */, queue, resolved);
2932
2308
  } else {
2933
- await this.handler.playSong(voiceChannel, song, { textChannel, skip, position });
2309
+ if (!this.options.nsfw && resolved.ageRestricted && !isNsfwChannel(queue?.textChannel || textChannel)) {
2310
+ throw new DisTubeError("NON_NSFW");
2311
+ }
2312
+ this.debug(`[${queue.id}] Adding song to queue: ${resolved.name || resolved.url || resolved.id || resolved}`);
2313
+ queue.addToQueue(resolved, position);
2314
+ if (queue.playing || this.options.emitAddSongWhenCreatingQueue) this.emit("addSong" /* ADD_SONG */, queue, resolved);
2934
2315
  }
2316
+ if (!queue.playing) await queue.play();
2935
2317
  } catch (e) {
2936
2318
  if (!(e instanceof DisTubeError)) {
2319
+ this.debug(`[${queue.id}] Unexpected error while playing song: ${e.stack || e.message}`);
2937
2320
  try {
2938
2321
  e.name = "PlayError";
2939
2322
  e.message = `${typeof song === "string" ? song : song.url}
@@ -2943,43 +2326,24 @@ ${e.message}`;
2943
2326
  }
2944
2327
  throw e;
2945
2328
  } finally {
2946
- if (queuing)
2947
- queue?._taskQueue.resolve();
2329
+ queue._taskQueue.resolve();
2948
2330
  }
2949
2331
  }
2950
2332
  /**
2951
2333
  * Create a custom playlist
2952
- *
2953
- * @example
2954
- * ```ts
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
- * ```ts
2963
- *
2964
- * @param songs - Array of url, Song or SearchResult
2334
+ * @param songs - Array of url or Song
2965
2335
  * @param options - Optional options
2966
2336
  */
2967
- async createCustomPlaylist(songs, options = {}) {
2968
- const { member, properties, parallel, metadata } = { parallel: true, ...options };
2969
- if (!Array.isArray(songs))
2970
- throw new DisTubeError("INVALID_TYPE", "Array", songs, "songs");
2971
- if (!songs.length)
2972
- throw new DisTubeError("EMPTY_ARRAY", "songs");
2973
- const filteredSongs = songs.filter(
2974
- (song) => song instanceof Song || isURL(song) || typeof song !== "string" && song.type === "video" /* VIDEO */
2975
- );
2976
- if (!filteredSongs.length)
2977
- throw new DisTubeError("NO_VALID_SONG");
2337
+ async createCustomPlaylist(songs, { member, parallel, metadata, name, source, url, thumbnail } = {}) {
2338
+ if (!Array.isArray(songs)) throw new DisTubeError("INVALID_TYPE", "Array", songs, "songs");
2339
+ if (!songs.length) throw new DisTubeError("EMPTY_ARRAY", "songs");
2340
+ const filteredSongs = songs.filter((song) => song instanceof Song || isURL(song));
2341
+ if (!filteredSongs.length) throw new DisTubeError("NO_VALID_SONG");
2978
2342
  if (member && !isMemberInstance(member)) {
2979
2343
  throw new DisTubeError("INVALID_TYPE", "Discord.Member", member, "options.member");
2980
2344
  }
2981
2345
  let resolvedSongs;
2982
- if (parallel) {
2346
+ if (parallel !== false) {
2983
2347
  const promises = filteredSongs.map(
2984
2348
  (song) => this.handler.resolve(song, { member, metadata }).catch(() => void 0)
2985
2349
  );
@@ -2988,71 +2352,22 @@ ${e.message}`;
2988
2352
  resolvedSongs = [];
2989
2353
  for (const song of filteredSongs) {
2990
2354
  const resolved = await this.handler.resolve(song, { member, metadata }).catch(() => void 0);
2991
- if (resolved instanceof Song)
2992
- resolvedSongs.push(resolved);
2355
+ if (resolved instanceof Song) resolvedSongs.push(resolved);
2993
2356
  }
2994
2357
  }
2995
- return new Playlist(resolvedSongs, { member, properties, metadata });
2996
- }
2997
- /**
2998
- * Search for a song. You can customize how user answers instead of send a number.
2999
- * Then use {@link DisTube#play} to play it.
3000
- *
3001
- * @param string - The string search for
3002
- * @param options - Search options
3003
- * @param options.limit - Limit the results
3004
- * @param options.type - Type of results (`video` or `playlist`).
3005
- * @param options.safeSearch - Whether or not use safe search (YouTube restricted mode)
3006
- *
3007
- * @returns Array of results
3008
- */
3009
- async search(string, options = {}) {
3010
- const opts = { type: "video" /* VIDEO */, limit: 10, safeSearch: false, ...options };
3011
- if (typeof opts.type !== "string" || !["video", "playlist"].includes(opts.type)) {
3012
- throw new DisTubeError("INVALID_TYPE", ["video", "playlist"], opts.type, "options.type");
3013
- }
3014
- if (typeof opts.limit !== "number")
3015
- throw new DisTubeError("INVALID_TYPE", "number", opts.limit, "options.limit");
3016
- if (opts.limit < 1)
3017
- throw new DisTubeError("NUMBER_COMPARE", "option.limit", "bigger or equal to", 1);
3018
- if (typeof opts.safeSearch !== "boolean") {
3019
- throw new DisTubeError("INVALID_TYPE", "boolean", opts.safeSearch, "options.safeSearch");
3020
- }
3021
- try {
3022
- const search = await (0, import_ytsr.default)(string, { ...opts, requestOptions: { headers: { cookie: this.handler.ytCookie } } });
3023
- const results = search.items.map((i) => {
3024
- if (i.type === "video")
3025
- return new SearchResultVideo(i);
3026
- return new SearchResultPlaylist(i);
3027
- });
3028
- if (results.length === 0)
3029
- throw new DisTubeError("NO_RESULT");
3030
- return results;
3031
- } catch (e) {
3032
- if (options.retried)
3033
- throw e;
3034
- options.retried = true;
3035
- return this.search(string, options);
3036
- }
2358
+ return new Playlist(
2359
+ {
2360
+ source: source || "custom",
2361
+ name,
2362
+ url,
2363
+ thumbnail: thumbnail || resolvedSongs.find((s) => s.thumbnail)?.thumbnail,
2364
+ songs: resolvedSongs
2365
+ },
2366
+ { member, metadata }
2367
+ );
3037
2368
  }
3038
2369
  /**
3039
2370
  * Get the guild queue
3040
- *
3041
- * @example
3042
- * ```ts
3043
- * client.on('message', (message) => {
3044
- * if (!message.content.startsWith(config.prefix)) return;
3045
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3046
- * const command = args.shift();
3047
- * if (command == "queue") {
3048
- * const queue = distube.getQueue(message);
3049
- * message.channel.send('Current queue:\n' + queue.songs.map((song, id) =>
3050
- * `**${id+1}**. [${song.name}](${song.url}) - \`${song.formattedDuration}\``
3051
- * ).join("\n"));
3052
- * }
3053
- * });
3054
- * ```ts
3055
- *
3056
2371
  * @param guild - The type can be resolved to give a {@link Queue}
3057
2372
  */
3058
2373
  getQueue(guild) {
@@ -3060,288 +2375,220 @@ ${e.message}`;
3060
2375
  }
3061
2376
  /**
3062
2377
  * Pause the guild stream
3063
- *
3064
2378
  * @param guild - The type can be resolved to give a {@link Queue}
3065
- *
3066
2379
  * @returns The guild queue
3067
2380
  */
3068
2381
  pause(guild) {
3069
- return __privateMethod(this, _getQueue, getQueue_fn).call(this, guild).pause();
2382
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).pause();
3070
2383
  }
3071
2384
  /**
3072
2385
  * Resume the guild stream
3073
- *
3074
2386
  * @param guild - The type can be resolved to give a {@link Queue}
3075
- *
3076
2387
  * @returns The guild queue
3077
2388
  */
3078
2389
  resume(guild) {
3079
- return __privateMethod(this, _getQueue, getQueue_fn).call(this, guild).resume();
2390
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).resume();
3080
2391
  }
3081
2392
  /**
3082
2393
  * Stop the guild stream
3083
- *
3084
- * @example
3085
- * ```ts
3086
- * client.on('message', (message) => {
3087
- * if (!message.content.startsWith(config.prefix)) return;
3088
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3089
- * const command = args.shift();
3090
- * if (command == "stop") {
3091
- * distube.stop(message);
3092
- * message.channel.send("Stopped the queue!");
3093
- * }
3094
- * });
3095
- * ```ts
3096
- *
3097
2394
  * @param guild - The type can be resolved to give a {@link Queue}
3098
2395
  */
3099
2396
  stop(guild) {
3100
- return __privateMethod(this, _getQueue, getQueue_fn).call(this, guild).stop();
2397
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).stop();
3101
2398
  }
3102
2399
  /**
3103
2400
  * Set the guild stream's volume
3104
- *
3105
- * @example
3106
- * ```ts
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
- * ```ts
3115
- *
3116
2401
  * @param guild - The type can be resolved to give a {@link Queue}
3117
2402
  * @param percent - The percentage of volume you want to set
3118
- *
3119
2403
  * @returns The guild queue
3120
2404
  */
3121
2405
  setVolume(guild, percent) {
3122
- return __privateMethod(this, _getQueue, getQueue_fn).call(this, guild).setVolume(percent);
2406
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).setVolume(percent);
3123
2407
  }
3124
2408
  /**
3125
2409
  * Skip the playing song if there is a next song in the queue. <info>If {@link
3126
2410
  * Queue#autoplay} is `true` and there is no up next song, DisTube will add and
3127
2411
  * play a related song.</info>
3128
- *
3129
- * @example
3130
- * ```ts
3131
- * client.on('message', (message) => {
3132
- * if (!message.content.startsWith(config.prefix)) return;
3133
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3134
- * const command = args.shift();
3135
- * if (command == "skip")
3136
- * distube.skip(message);
3137
- * });
3138
- * ```ts
3139
- *
3140
2412
  * @param guild - The type can be resolved to give a {@link Queue}
3141
- *
3142
2413
  * @returns The new Song will be played
3143
2414
  */
3144
2415
  skip(guild) {
3145
- return __privateMethod(this, _getQueue, getQueue_fn).call(this, guild).skip();
2416
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).skip();
3146
2417
  }
3147
2418
  /**
3148
2419
  * Play the previous song
3149
- *
3150
- * @example
3151
- * ```ts
3152
- * client.on('message', (message) => {
3153
- * if (!message.content.startsWith(config.prefix)) return;
3154
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3155
- * const command = args.shift();
3156
- * if (command == "previous")
3157
- * distube.previous(message);
3158
- * });
3159
- * ```ts
3160
- *
3161
2420
  * @param guild - The type can be resolved to give a {@link Queue}
3162
- *
3163
2421
  * @returns The new Song will be played
3164
2422
  */
3165
2423
  previous(guild) {
3166
- return __privateMethod(this, _getQueue, getQueue_fn).call(this, guild).previous();
2424
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).previous();
3167
2425
  }
3168
2426
  /**
3169
2427
  * Shuffle the guild queue songs
3170
- *
3171
- * @example
3172
- * ```ts
3173
- * client.on('message', (message) => {
3174
- * if (!message.content.startsWith(config.prefix)) return;
3175
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3176
- * const command = args.shift();
3177
- * if (command == "shuffle")
3178
- * distube.shuffle(message);
3179
- * });
3180
- * ```ts
3181
- *
3182
2428
  * @param guild - The type can be resolved to give a {@link Queue}
3183
- *
3184
2429
  * @returns The guild queue
3185
2430
  */
3186
2431
  shuffle(guild) {
3187
- return __privateMethod(this, _getQueue, getQueue_fn).call(this, guild).shuffle();
2432
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).shuffle();
3188
2433
  }
3189
2434
  /**
3190
2435
  * Jump to the song number in the queue. The next one is 1, 2,... The previous one
3191
2436
  * is -1, -2,...
3192
- *
3193
- * @example
3194
- * ```ts
3195
- * client.on('message', (message) => {
3196
- * if (!message.content.startsWith(config.prefix)) return;
3197
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3198
- * const command = args.shift();
3199
- * if (command == "jump")
3200
- * distube.jump(message, parseInt(args[0]))
3201
- * .catch(err => message.channel.send("Invalid song number."));
3202
- * });
3203
- * ```ts
3204
- *
3205
2437
  * @param guild - The type can be resolved to give a {@link Queue}
3206
2438
  * @param num - The song number to play
3207
- *
3208
2439
  * @returns The new Song will be played
3209
2440
  */
3210
2441
  jump(guild, num) {
3211
- return __privateMethod(this, _getQueue, getQueue_fn).call(this, guild).jump(num);
2442
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).jump(num);
3212
2443
  }
3213
2444
  /**
3214
2445
  * Set the repeat mode of the guild queue.
3215
2446
  * Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
3216
- *
3217
- * @example
3218
- * ```ts
3219
- * client.on('message', (message) => {
3220
- * if (!message.content.startsWith(config.prefix)) return;
3221
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3222
- * const command = args.shift();
3223
- * if (command == "repeat") {
3224
- * let mode = distube.setRepeatMode(message, parseInt(args[0]));
3225
- * mode = mode ? mode == 2 ? "Repeat queue" : "Repeat song" : "Off";
3226
- * message.channel.send("Set repeat mode to `" + mode + "`");
3227
- * }
3228
- * });
3229
- * ```ts
3230
- * @example
3231
- * ```ts
3232
- * const { RepeatMode } = require("distube");
3233
- * let mode;
3234
- * switch(distube.setRepeatMode(message, parseInt(args[0]))) {
3235
- * case RepeatMode.DISABLED:
3236
- * mode = "Off";
3237
- * break;
3238
- * case RepeatMode.SONG:
3239
- * mode = "Repeat a song";
3240
- * break;
3241
- * case RepeatMode.QUEUE:
3242
- * mode = "Repeat all queue";
3243
- * break;
3244
- * }
3245
- * message.channel.send("Set repeat mode to `" + mode + "`");
3246
- * ```ts
3247
- *
3248
2447
  * @param guild - The type can be resolved to give a {@link Queue}
3249
2448
  * @param mode - The repeat modes (toggle if `undefined`)
3250
- *
3251
2449
  * @returns The new repeat mode
3252
2450
  */
3253
2451
  setRepeatMode(guild, mode) {
3254
- return __privateMethod(this, _getQueue, getQueue_fn).call(this, guild).setRepeatMode(mode);
2452
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).setRepeatMode(mode);
3255
2453
  }
3256
2454
  /**
3257
2455
  * Toggle autoplay mode
3258
- *
3259
- * @example
3260
- * ```ts
3261
- * client.on('message', (message) => {
3262
- * if (!message.content.startsWith(config.prefix)) return;
3263
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3264
- * const command = args.shift();
3265
- * if (command == "autoplay") {
3266
- * const mode = distube.toggleAutoplay(message);
3267
- * message.channel.send("Set autoplay mode to `" + (mode ? "On" : "Off") + "`");
3268
- * }
3269
- * });
3270
- * ```ts
3271
- *
3272
2456
  * @param guild - The type can be resolved to give a {@link Queue}
3273
- *
3274
2457
  * @returns Autoplay mode state
3275
2458
  */
3276
2459
  toggleAutoplay(guild) {
3277
- const queue = __privateMethod(this, _getQueue, getQueue_fn).call(this, guild);
2460
+ const queue = __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild);
3278
2461
  queue.autoplay = !queue.autoplay;
3279
2462
  return queue.autoplay;
3280
2463
  }
3281
2464
  /**
3282
2465
  * Add related song to the queue
3283
- *
3284
2466
  * @param guild - The type can be resolved to give a {@link Queue}
3285
- *
3286
2467
  * @returns The guild queue
3287
2468
  */
3288
2469
  addRelatedSong(guild) {
3289
- return __privateMethod(this, _getQueue, getQueue_fn).call(this, guild).addRelatedSong();
2470
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).addRelatedSong();
3290
2471
  }
3291
2472
  /**
3292
2473
  * Set the playing time to another position
3293
- *
3294
- * @example
3295
- * ```ts
3296
- * client.on('message', message => {
3297
- * if (!message.content.startsWith(config.prefix)) return;
3298
- * const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
3299
- * const command = args.shift();
3300
- * if (command = 'seek')
3301
- * distube.seek(message, Number(args[0]));
3302
- * });
3303
- * ```ts
3304
- *
3305
2474
  * @param guild - The type can be resolved to give a {@link Queue}
3306
2475
  * @param time - Time in seconds
3307
- *
3308
2476
  * @returns Seeked queue
3309
2477
  */
3310
2478
  seek(guild, time) {
3311
- return __privateMethod(this, _getQueue, getQueue_fn).call(this, guild).seek(time);
2479
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).seek(time);
3312
2480
  }
3313
2481
  /**
3314
2482
  * Emit error event
3315
- *
3316
2483
  * @param error - error
3317
- * @param channel - Text channel where the error is encountered.
2484
+ * @param queue - The queue encountered the error
2485
+ * @param song - The playing song when encountered the error
3318
2486
  */
3319
- emitError(error, channel) {
3320
- if (this.listeners("error" /* ERROR */).length) {
3321
- this.emit("error" /* ERROR */, channel, error);
3322
- } else {
3323
- console.error(error);
3324
- console.warn("Unhandled 'error' event.");
3325
- console.warn(
3326
- "See: https://distube.js.org/classes/DisTube.html#error and https://nodejs.org/api/events.html#events_error_events"
3327
- );
3328
- }
2487
+ emitError(error, queue, song) {
2488
+ this.emit("error" /* ERROR */, error, queue, song);
2489
+ }
2490
+ /**
2491
+ * Emit debug event
2492
+ * @param message - debug message
2493
+ */
2494
+ debug(message) {
2495
+ this.emit("debug" /* DEBUG */, message);
3329
2496
  }
3330
2497
  };
3331
- _getQueue = new WeakSet();
2498
+ _DisTube_instances = new WeakSet();
3332
2499
  getQueue_fn = /* @__PURE__ */ __name(function(guild) {
3333
2500
  const queue = this.getQueue(guild);
3334
- if (!queue)
3335
- throw new DisTubeError("NO_QUEUE");
2501
+ if (!queue) throw new DisTubeError("NO_QUEUE");
3336
2502
  return queue;
3337
2503
  }, "#getQueue");
3338
2504
  __name(_DisTube, "DisTube");
2505
+ /**
2506
+ * @event
2507
+ * Emitted after DisTube add a new playlist to the playing {@link Queue}.
2508
+ * @param queue - The guild queue
2509
+ * @param playlist - Playlist info
2510
+ */
2511
+ __publicField(_DisTube, _l);
2512
+ /**
2513
+ * @event
2514
+ * Emitted after DisTube add a new song to the playing {@link Queue}.
2515
+ * @param queue - The guild queue
2516
+ * @param song - Added song
2517
+ */
2518
+ __publicField(_DisTube, _k);
2519
+ /**
2520
+ * @event
2521
+ * Emitted when a {@link Queue} is deleted with any reasons.
2522
+ * @param queue - The guild queue
2523
+ */
2524
+ __publicField(_DisTube, _j);
2525
+ /**
2526
+ * @event
2527
+ * Emitted when the bot is disconnected to a voice channel.
2528
+ * @param queue - The guild queue
2529
+ */
2530
+ __publicField(_DisTube, _i);
2531
+ /**
2532
+ * @event
2533
+ * Emitted when DisTube encounters an error while playing songs.
2534
+ * @param error - error
2535
+ * @param queue - The queue encountered the error
2536
+ * @param song - The playing song when encountered the error
2537
+ */
2538
+ __publicField(_DisTube, _h);
2539
+ /**
2540
+ * @event
2541
+ * Emitted for logging FFmpeg debug information.
2542
+ * @param debug - Debug message string.
2543
+ */
2544
+ __publicField(_DisTube, _g);
2545
+ /**
2546
+ * @event
2547
+ * Emitted to provide debug information from DisTube's operation.
2548
+ * Useful for troubleshooting or logging purposes.
2549
+ *
2550
+ * @param debug - Debug message string.
2551
+ */
2552
+ __publicField(_DisTube, _f);
2553
+ /**
2554
+ * @event
2555
+ * Emitted when there is no more song in the queue and {@link Queue#autoplay} is `false`.
2556
+ * @param queue - The guild queue
2557
+ */
2558
+ __publicField(_DisTube, _e);
2559
+ /**
2560
+ * @event
2561
+ * Emitted when DisTube finished a song.
2562
+ * @param queue - The guild queue
2563
+ * @param song - Finished song
2564
+ */
2565
+ __publicField(_DisTube, _d);
2566
+ /**
2567
+ * @event
2568
+ * Emitted when DisTube initialize a queue to change queue default properties.
2569
+ * @param queue - The guild queue
2570
+ */
2571
+ __publicField(_DisTube, _c);
2572
+ /**
2573
+ * @event
2574
+ * Emitted when {@link Queue#autoplay} is `true`, {@link Queue#songs} is empty, and
2575
+ * DisTube cannot find related songs to play.
2576
+ * @param queue - The guild queue
2577
+ */
2578
+ __publicField(_DisTube, _b);
2579
+ /**
2580
+ * @event
2581
+ * Emitted when DisTube play a song.
2582
+ * If {@link DisTubeOptions}.emitNewSongOnly is `true`, this event is not emitted
2583
+ * when looping a song or next song is the previous one.
2584
+ * @param queue - The guild queue
2585
+ * @param song - Playing song
2586
+ */
2587
+ __publicField(_DisTube, _a);
3339
2588
  var DisTube = _DisTube;
3340
2589
  // Annotate the CommonJS export names for ESM import in node:
3341
2590
  0 && (module.exports = {
3342
2591
  BaseManager,
3343
- CustomPlugin,
3344
- DirectLinkPlugin,
3345
2592
  DisTube,
3346
2593
  DisTubeBase,
3347
2594
  DisTubeError,
@@ -3353,23 +2600,20 @@ var DisTube = _DisTube;
3353
2600
  ExtractorPlugin,
3354
2601
  FilterManager,
3355
2602
  GuildIdManager,
2603
+ InfoExtractorPlugin,
3356
2604
  Options,
2605
+ PlayableExtractorPlugin,
3357
2606
  Playlist,
3358
2607
  Plugin,
3359
2608
  PluginType,
3360
2609
  Queue,
3361
2610
  QueueManager,
3362
2611
  RepeatMode,
3363
- SearchResultPlaylist,
3364
- SearchResultType,
3365
- SearchResultVideo,
3366
2612
  Song,
3367
- StreamType,
3368
2613
  TaskQueue,
3369
2614
  checkFFmpeg,
3370
2615
  checkIntents,
3371
2616
  checkInvalidKey,
3372
- chooseBestVideoFormat,
3373
2617
  defaultFilters,
3374
2618
  defaultOptions,
3375
2619
  formatDuration,
@@ -3379,7 +2623,6 @@ var DisTube = _DisTube;
3379
2623
  isMessageInstance,
3380
2624
  isNsfwChannel,
3381
2625
  isObject,
3382
- isRecord,
3383
2626
  isSnowflake,
3384
2627
  isSupportedVoiceChannel,
3385
2628
  isTextChannelInstance,
@@ -3387,9 +2630,7 @@ var DisTube = _DisTube;
3387
2630
  isURL,
3388
2631
  isVoiceChannelEmpty,
3389
2632
  objectKeys,
3390
- parseNumber,
3391
2633
  resolveGuildId,
3392
- toSecond,
3393
2634
  version
3394
2635
  });
3395
2636
  //# sourceMappingURL=index.js.map