distube 4.2.2 → 5.0.1

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