distube 4.2.2 → 5.0.0

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