odistube 5.0.2

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.

Potentially problematic release.


This version of odistube might be problematic. Click here for more details.

package/dist/index.js ADDED
@@ -0,0 +1,2636 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __typeError = (msg) => {
7
+ throw TypeError(msg);
8
+ };
9
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
10
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
11
+ var __commonJS = (cb, mod) => function __require() {
12
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
13
+ };
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
17
+ };
18
+ var __copyProps = (to, from, except, desc) => {
19
+ if (from && typeof from === "object" || typeof from === "function") {
20
+ for (let key of __getOwnPropNames(from))
21
+ if (!__hasOwnProp.call(to, key) && key !== except)
22
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
23
+ }
24
+ return to;
25
+ };
26
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
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);
33
+
34
+ // package.json
35
+ var require_package = __commonJS({
36
+ "package.json"(exports2, module2) {
37
+ module2.exports = {
38
+ name: "distube",
39
+ version: "5.0.2",
40
+ description: "A powerful Discord.js module for simplifying music commands and effortless playback of various sources with integrated audio filters.",
41
+ main: "./dist/index.js",
42
+ types: "./dist/index.d.ts",
43
+ exports: "./dist/index.js",
44
+ directories: {
45
+ lib: "src",
46
+ test: "tests"
47
+ },
48
+ files: [
49
+ "dist"
50
+ ],
51
+ scripts: {
52
+ test: "VITE_CJS_IGNORE_WARNING=true vitest run",
53
+ docs: "typedoc",
54
+ lint: "prettier --check . && eslint .",
55
+ "lint:fix": "eslint . --fix",
56
+ prettier: 'prettier --write "**/*.{ts,json,yml,yaml,md}"',
57
+ build: "tsup",
58
+ type: "tsc --noEmit",
59
+ update: 'pnpm up -L "!eslint"',
60
+ prepare: "husky",
61
+ prepublishOnly: "pnpm run lint && pnpm run test",
62
+ prepack: "pnpm run build"
63
+ },
64
+ repository: {
65
+ type: "git",
66
+ url: "git+https://github.com/skick1234/DisTube.git"
67
+ },
68
+ keywords: [
69
+ "youtube",
70
+ "music",
71
+ "discord",
72
+ "discordjs",
73
+ "bot",
74
+ "distube",
75
+ "queue",
76
+ "musicbot",
77
+ "discord-music-bot",
78
+ "music-bot",
79
+ "discord-js"
80
+ ],
81
+ author: "Skick (https://github.com/skick1234)",
82
+ license: "MIT",
83
+ bugs: {
84
+ url: "https://github.com/skick1234/DisTube/issues"
85
+ },
86
+ funding: "https://github.com/skick1234/DisTube?sponsor",
87
+ homepage: "https://distube.js.org/",
88
+ dependencies: {
89
+ "tiny-typed-emitter": "^2.1.0",
90
+ undici: "^6.18.2"
91
+ },
92
+ devDependencies: {
93
+ "@commitlint/cli": "^19.3.0",
94
+ "@commitlint/config-conventional": "^19.2.2",
95
+ "@discordjs/voice": "^0.17.0",
96
+ "@types/node": "^20.14.2",
97
+ "@types/tough-cookie": "^4.0.5",
98
+ "@typescript-eslint/eslint-plugin": "^7.13.0",
99
+ "@typescript-eslint/parser": "^7.13.0",
100
+ "@vitest/coverage-v8": "^1.6.0",
101
+ "discord.js": "^14.15.3",
102
+ eslint: "^8.57.0",
103
+ "eslint-config-distube": "^1.7.0",
104
+ husky: "^9.0.11",
105
+ "nano-staged": "^0.8.0",
106
+ prettier: "^3.3.2",
107
+ "sodium-native": "^4.1.1",
108
+ "ts-node": "^10.9.2",
109
+ tsup: "^8.1.0",
110
+ typedoc: "^0.25.13",
111
+ "typedoc-material-theme": "^1.0.2",
112
+ "typedoc-plugin-extras": "^3.0.0",
113
+ typescript: "^5.4.5",
114
+ "vite-tsconfig-paths": "^4.3.2",
115
+ vitest: "^1.6.0"
116
+ },
117
+ peerDependencies: {
118
+ "@discordjs/voice": "*",
119
+ "discord.js": "14"
120
+ },
121
+ "nano-staged": {
122
+ "*.ts": [
123
+ "prettier --write",
124
+ "eslint"
125
+ ],
126
+ "*.{json,yml,yaml,md}": [
127
+ "prettier --write"
128
+ ]
129
+ },
130
+ engines: {
131
+ node: ">=18.17"
132
+ }
133
+ };
134
+ }
135
+ });
136
+
137
+ // src/index.ts
138
+ var src_exports = {};
139
+ __export(src_exports, {
140
+ BaseManager: () => BaseManager,
141
+ DisTube: () => DisTube,
142
+ DisTubeBase: () => DisTubeBase,
143
+ DisTubeError: () => DisTubeError,
144
+ DisTubeHandler: () => DisTubeHandler,
145
+ DisTubeStream: () => DisTubeStream,
146
+ DisTubeVoice: () => DisTubeVoice,
147
+ DisTubeVoiceManager: () => DisTubeVoiceManager,
148
+ Events: () => Events,
149
+ ExtractorPlugin: () => ExtractorPlugin,
150
+ FilterManager: () => FilterManager,
151
+ GuildIdManager: () => GuildIdManager,
152
+ InfoExtractorPlugin: () => InfoExtractorPlugin,
153
+ Options: () => Options,
154
+ PlayableExtractorPlugin: () => PlayableExtractorPlugin,
155
+ Playlist: () => Playlist,
156
+ Plugin: () => Plugin,
157
+ PluginType: () => PluginType,
158
+ Queue: () => Queue,
159
+ QueueManager: () => QueueManager,
160
+ RepeatMode: () => RepeatMode,
161
+ Song: () => Song,
162
+ TaskQueue: () => TaskQueue,
163
+ checkFFmpeg: () => checkFFmpeg,
164
+ checkIntents: () => checkIntents,
165
+ checkInvalidKey: () => checkInvalidKey,
166
+ default: () => DisTube,
167
+ defaultFilters: () => defaultFilters,
168
+ defaultOptions: () => defaultOptions,
169
+ formatDuration: () => formatDuration,
170
+ isClientInstance: () => isClientInstance,
171
+ isGuildInstance: () => isGuildInstance,
172
+ isMemberInstance: () => isMemberInstance,
173
+ isMessageInstance: () => isMessageInstance,
174
+ isNsfwChannel: () => isNsfwChannel,
175
+ isObject: () => isObject,
176
+ isSnowflake: () => isSnowflake,
177
+ isSupportedVoiceChannel: () => isSupportedVoiceChannel,
178
+ isTextChannelInstance: () => isTextChannelInstance,
179
+ isTruthy: () => isTruthy,
180
+ isURL: () => isURL,
181
+ isVoiceChannelEmpty: () => isVoiceChannelEmpty,
182
+ objectKeys: () => objectKeys,
183
+ resolveGuildId: () => resolveGuildId,
184
+ version: () => version
185
+ });
186
+ module.exports = __toCommonJS(src_exports);
187
+
188
+ // src/type.ts
189
+ var Events = /* @__PURE__ */ ((Events2) => {
190
+ Events2["ERROR"] = "error";
191
+ Events2["ADD_LIST"] = "addList";
192
+ Events2["ADD_SONG"] = "addSong";
193
+ Events2["PLAY_SONG"] = "playSong";
194
+ Events2["FINISH_SONG"] = "finishSong";
195
+ Events2["EMPTY"] = "empty";
196
+ Events2["FINISH"] = "finish";
197
+ Events2["INIT_QUEUE"] = "initQueue";
198
+ Events2["NO_RELATED"] = "noRelated";
199
+ Events2["DISCONNECT"] = "disconnect";
200
+ Events2["DELETE_QUEUE"] = "deleteQueue";
201
+ Events2["FFMPEG_DEBUG"] = "ffmpegDebug";
202
+ Events2["DEBUG"] = "debug";
203
+ return Events2;
204
+ })(Events || {});
205
+ var RepeatMode = /* @__PURE__ */ ((RepeatMode2) => {
206
+ RepeatMode2[RepeatMode2["DISABLED"] = 0] = "DISABLED";
207
+ RepeatMode2[RepeatMode2["SONG"] = 1] = "SONG";
208
+ RepeatMode2[RepeatMode2["QUEUE"] = 2] = "QUEUE";
209
+ return RepeatMode2;
210
+ })(RepeatMode || {});
211
+ var PluginType = /* @__PURE__ */ ((PluginType2) => {
212
+ PluginType2["EXTRACTOR"] = "extractor";
213
+ PluginType2["INFO_EXTRACTOR"] = "info-extractor";
214
+ PluginType2["PLAYABLE_EXTRACTOR"] = "playable-extractor";
215
+ return PluginType2;
216
+ })(PluginType || {});
217
+
218
+ // src/constant.ts
219
+ var defaultFilters = {
220
+ "3d": "apulsator=hz=0.125",
221
+ bassboost: "bass=g=10",
222
+ echo: "aecho=0.8:0.9:1000:0.3",
223
+ flanger: "flanger",
224
+ gate: "agate",
225
+ haas: "haas",
226
+ karaoke: "stereotools=mlev=0.1",
227
+ nightcore: "asetrate=48000*1.25,aresample=48000,bass=g=5",
228
+ reverse: "areverse",
229
+ vaporwave: "asetrate=48000*0.8,aresample=48000,atempo=1.1",
230
+ mcompand: "mcompand",
231
+ phaser: "aphaser",
232
+ tremolo: "tremolo",
233
+ surround: "surround",
234
+ earwax: "earwax"
235
+ };
236
+ var defaultOptions = {
237
+ plugins: [],
238
+ emitNewSongOnly: false,
239
+ savePreviousSongs: true,
240
+ nsfw: false,
241
+ emitAddSongWhenCreatingQueue: true,
242
+ emitAddListWhenCreatingQueue: true,
243
+ joinNewVoiceChannel: true
244
+ };
245
+
246
+ // src/struct/DisTubeError.ts
247
+ var import_node_util = require("util");
248
+ var ERROR_MESSAGES = {
249
+ 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"),
250
+ NUMBER_COMPARE: /* @__PURE__ */ __name((name, expected, value) => `'${name}' must be ${expected} ${value}`, "NUMBER_COMPARE"),
251
+ EMPTY_ARRAY: /* @__PURE__ */ __name((name) => `'${name}' is an empty array`, "EMPTY_ARRAY"),
252
+ EMPTY_FILTERED_ARRAY: /* @__PURE__ */ __name((name, type) => `There is no valid '${type}' in the '${name}' array`, "EMPTY_FILTERED_ARRAY"),
253
+ EMPTY_STRING: /* @__PURE__ */ __name((name) => `'${name}' string must not be empty`, "EMPTY_STRING"),
254
+ INVALID_KEY: /* @__PURE__ */ __name((obj, key) => `'${key}' does not need to be provided in ${obj}`, "INVALID_KEY"),
255
+ MISSING_KEY: /* @__PURE__ */ __name((obj, key) => `'${key}' needs to be provided in ${obj}`, "MISSING_KEY"),
256
+ MISSING_KEYS: /* @__PURE__ */ __name((obj, key, all) => `${key.map((k) => `'${k}'`).join(all ? " and " : " or ")} need to be provided in ${obj}`, "MISSING_KEYS"),
257
+ MISSING_INTENTS: /* @__PURE__ */ __name((i) => `${i} intent must be provided for the Client`, "MISSING_INTENTS"),
258
+ DISABLED_OPTION: /* @__PURE__ */ __name((o) => `DisTubeOptions.${o} is disabled`, "DISABLED_OPTION"),
259
+ ENABLED_OPTION: /* @__PURE__ */ __name((o) => `DisTubeOptions.${o} is enabled`, "ENABLED_OPTION"),
260
+ NOT_IN_VOICE: "User is not in any voice channel",
261
+ VOICE_FULL: "The voice channel is full",
262
+ VOICE_ALREADY_CREATED: "This guild already has a voice connection which is not managed by DisTube",
263
+ VOICE_CONNECT_FAILED: /* @__PURE__ */ __name((s) => `Cannot connect to the voice channel after ${s} seconds`, "VOICE_CONNECT_FAILED"),
264
+ VOICE_MISSING_PERMS: "I do not have permission to join this voice channel",
265
+ VOICE_RECONNECT_FAILED: "Cannot reconnect to the voice channel",
266
+ VOICE_DIFFERENT_GUILD: "Cannot join a voice channel in a different guild",
267
+ VOICE_DIFFERENT_CLIENT: "Cannot join a voice channel created by a different client",
268
+ FFMPEG_EXITED: /* @__PURE__ */ __name((code) => `ffmpeg exited with code ${code}`, "FFMPEG_EXITED"),
269
+ FFMPEG_NOT_INSTALLED: /* @__PURE__ */ __name((path) => `ffmpeg is not installed at '${path}' path`, "FFMPEG_NOT_INSTALLED"),
270
+ NO_QUEUE: "There is no playing queue in this guild",
271
+ QUEUE_EXIST: "This guild has a Queue already",
272
+ QUEUE_STOPPED: "The queue has been stopped already",
273
+ PAUSED: "The queue has been paused already",
274
+ RESUMED: "The queue has been playing already",
275
+ NO_PREVIOUS: "There is no previous song in this queue",
276
+ NO_UP_NEXT: "There is no up next song",
277
+ NO_SONG_POSITION: "Does not have any song at this position",
278
+ NO_PLAYING_SONG: "There is no playing song in the queue",
279
+ NO_RELATED: "Cannot find any related songs",
280
+ CANNOT_PLAY_RELATED: "Cannot play the related song",
281
+ UNAVAILABLE_VIDEO: "This video is unavailable",
282
+ UNPLAYABLE_FORMATS: "No playable format found",
283
+ NON_NSFW: "Cannot play age-restricted content in non-NSFW channel",
284
+ NOT_SUPPORTED_URL: "This url is not supported",
285
+ NOT_SUPPORTED_SONG: /* @__PURE__ */ __name((song) => `There is no plugin supporting this song (${song})`, "NOT_SUPPORTED_SONG"),
286
+ NO_VALID_SONG: "'songs' array does not have any valid Song or url",
287
+ CANNOT_RESOLVE_SONG: /* @__PURE__ */ __name((t) => `Cannot resolve ${(0, import_node_util.inspect)(t)} to a Song`, "CANNOT_RESOLVE_SONG"),
288
+ CANNOT_GET_STREAM_URL: /* @__PURE__ */ __name((song) => `Cannot get stream url from this song (${song})`, "CANNOT_GET_STREAM_URL"),
289
+ CANNOT_GET_SEARCH_QUERY: /* @__PURE__ */ __name((song) => `Cannot get search query from this song (${song})`, "CANNOT_GET_SEARCH_QUERY"),
290
+ NO_RESULT: /* @__PURE__ */ __name((query) => `Cannot get song stream from this query (${query})`, "NO_RESULT"),
291
+ NO_STREAM_URL: /* @__PURE__ */ __name((song) => `No stream url attached (${song})`, "NO_STREAM_URL"),
292
+ EMPTY_FILTERED_PLAYLIST: "There is no valid video in the playlist\nMaybe age-restricted contents is filtered because you are in non-NSFW channel",
293
+ EMPTY_PLAYLIST: "There is no valid video in the playlist"
294
+ };
295
+ var haveCode = /* @__PURE__ */ __name((code) => Object.keys(ERROR_MESSAGES).includes(code), "haveCode");
296
+ var parseMessage = /* @__PURE__ */ __name((m, ...args) => typeof m === "string" ? m : m(...args), "parseMessage");
297
+ var getErrorMessage = /* @__PURE__ */ __name((code, ...args) => haveCode(code) ? parseMessage(ERROR_MESSAGES[code], ...args) : args[0], "getErrorMessage");
298
+ var _DisTubeError = class _DisTubeError extends Error {
299
+ constructor(code, ...args) {
300
+ super(getErrorMessage(code, ...args));
301
+ __publicField(this, "errorCode");
302
+ this.errorCode = code;
303
+ if (Error.captureStackTrace) Error.captureStackTrace(this, _DisTubeError);
304
+ }
305
+ get name() {
306
+ return `DisTubeError [${this.errorCode}]`;
307
+ }
308
+ get code() {
309
+ return this.errorCode;
310
+ }
311
+ };
312
+ __name(_DisTubeError, "DisTubeError");
313
+ var DisTubeError = _DisTubeError;
314
+
315
+ // src/struct/TaskQueue.ts
316
+ var _Task = class _Task {
317
+ constructor() {
318
+ __publicField(this, "resolve");
319
+ __publicField(this, "promise");
320
+ this.promise = new Promise((res) => {
321
+ this.resolve = res;
322
+ });
323
+ }
324
+ };
325
+ __name(_Task, "Task");
326
+ var Task = _Task;
327
+ var _tasks;
328
+ var _TaskQueue = class _TaskQueue {
329
+ constructor() {
330
+ /**
331
+ * The task array
332
+ */
333
+ __privateAdd(this, _tasks, []);
334
+ }
335
+ /**
336
+ * Waits for last task finished and queues a new task
337
+ */
338
+ queuing() {
339
+ const next = this.remaining ? __privateGet(this, _tasks)[__privateGet(this, _tasks).length - 1].promise : Promise.resolve();
340
+ __privateGet(this, _tasks).push(new Task());
341
+ return next;
342
+ }
343
+ /**
344
+ * Removes the finished task and processes the next task
345
+ */
346
+ resolve() {
347
+ __privateGet(this, _tasks).shift()?.resolve();
348
+ }
349
+ /**
350
+ * The remaining number of tasks
351
+ */
352
+ get remaining() {
353
+ return __privateGet(this, _tasks).length;
354
+ }
355
+ };
356
+ _tasks = new WeakMap();
357
+ __name(_TaskQueue, "TaskQueue");
358
+ var TaskQueue = _TaskQueue;
359
+
360
+ // src/struct/Playlist.ts
361
+ var _metadata, _member;
362
+ var _Playlist = class _Playlist {
363
+ /**
364
+ * Create a Playlist
365
+ * @param playlist - Raw playlist info
366
+ * @param options - Optional data
367
+ */
368
+ constructor(playlist, { member, metadata } = {}) {
369
+ /**
370
+ * Playlist source.
371
+ */
372
+ __publicField(this, "source");
373
+ /**
374
+ * Songs in the playlist.
375
+ */
376
+ __publicField(this, "songs");
377
+ /**
378
+ * Playlist ID.
379
+ */
380
+ __publicField(this, "id");
381
+ /**
382
+ * Playlist name.
383
+ */
384
+ __publicField(this, "name");
385
+ /**
386
+ * Playlist URL.
387
+ */
388
+ __publicField(this, "url");
389
+ /**
390
+ * Playlist thumbnail.
391
+ */
392
+ __publicField(this, "thumbnail");
393
+ __privateAdd(this, _metadata);
394
+ __privateAdd(this, _member);
395
+ if (!Array.isArray(playlist.songs) || !playlist.songs.length) throw new DisTubeError("EMPTY_PLAYLIST");
396
+ this.source = playlist.source.toLowerCase();
397
+ this.songs = playlist.songs;
398
+ this.name = playlist.name;
399
+ this.id = playlist.id;
400
+ this.url = playlist.url;
401
+ this.thumbnail = playlist.thumbnail;
402
+ this.member = member;
403
+ this.songs.forEach((s) => s.playlist = this);
404
+ this.metadata = metadata;
405
+ }
406
+ /**
407
+ * Playlist duration in second.
408
+ */
409
+ get duration() {
410
+ return this.songs.reduce((prev, next) => prev + next.duration, 0);
411
+ }
412
+ /**
413
+ * Formatted duration string `hh:mm:ss`.
414
+ */
415
+ get formattedDuration() {
416
+ return formatDuration(this.duration);
417
+ }
418
+ /**
419
+ * User requested.
420
+ */
421
+ get member() {
422
+ return __privateGet(this, _member);
423
+ }
424
+ set member(member) {
425
+ if (!isMemberInstance(member)) return;
426
+ __privateSet(this, _member, member);
427
+ this.songs.forEach((s) => s.member = this.member);
428
+ }
429
+ /**
430
+ * User requested.
431
+ */
432
+ get user() {
433
+ return this.member?.user;
434
+ }
435
+ /**
436
+ * Optional metadata that can be used to identify the playlist.
437
+ */
438
+ get metadata() {
439
+ return __privateGet(this, _metadata);
440
+ }
441
+ set metadata(metadata) {
442
+ __privateSet(this, _metadata, metadata);
443
+ this.songs.forEach((s) => s.metadata = metadata);
444
+ }
445
+ toString() {
446
+ return `${this.name} (${this.songs.length} songs)`;
447
+ }
448
+ };
449
+ _metadata = new WeakMap();
450
+ _member = new WeakMap();
451
+ __name(_Playlist, "Playlist");
452
+ var Playlist = _Playlist;
453
+
454
+ // src/struct/Song.ts
455
+ var _metadata2, _member2, _playlist;
456
+ var _Song = class _Song {
457
+ /**
458
+ * Create a Song
459
+ *
460
+ * @param info - Raw song info
461
+ * @param options - Optional data
462
+ */
463
+ constructor(info, { member, metadata } = {}) {
464
+ /**
465
+ * The source of this song info
466
+ */
467
+ __publicField(this, "source");
468
+ /**
469
+ * Song ID.
470
+ */
471
+ __publicField(this, "id");
472
+ /**
473
+ * Song name.
474
+ */
475
+ __publicField(this, "name");
476
+ /**
477
+ * Indicates if the song is an active live.
478
+ */
479
+ __publicField(this, "isLive");
480
+ /**
481
+ * Song duration.
482
+ */
483
+ __publicField(this, "duration");
484
+ /**
485
+ * Formatted duration string (`hh:mm:ss`, `mm:ss` or `Live`).
486
+ */
487
+ __publicField(this, "formattedDuration");
488
+ /**
489
+ * Song URL.
490
+ */
491
+ __publicField(this, "url");
492
+ /**
493
+ * Song thumbnail.
494
+ */
495
+ __publicField(this, "thumbnail");
496
+ /**
497
+ * Song view count
498
+ */
499
+ __publicField(this, "views");
500
+ /**
501
+ * Song like count
502
+ */
503
+ __publicField(this, "likes");
504
+ /**
505
+ * Song dislike count
506
+ */
507
+ __publicField(this, "dislikes");
508
+ /**
509
+ * Song repost (share) count
510
+ */
511
+ __publicField(this, "reposts");
512
+ /**
513
+ * Song uploader
514
+ */
515
+ __publicField(this, "uploader");
516
+ /**
517
+ * Whether or not an age-restricted content
518
+ */
519
+ __publicField(this, "ageRestricted");
520
+ /**
521
+ * Stream info
522
+ */
523
+ __publicField(this, "stream");
524
+ /**
525
+ * The plugin that created this song
526
+ */
527
+ __publicField(this, "plugin");
528
+ __privateAdd(this, _metadata2);
529
+ __privateAdd(this, _member2);
530
+ __privateAdd(this, _playlist);
531
+ this.source = info.source.toLowerCase();
532
+ this.metadata = metadata;
533
+ this.member = member;
534
+ this.id = info.id;
535
+ this.name = info.name;
536
+ this.isLive = info.isLive;
537
+ this.duration = this.isLive || !info.duration ? 0 : info.duration;
538
+ this.formattedDuration = this.isLive ? "Live" : formatDuration(this.duration);
539
+ this.url = info.url;
540
+ this.thumbnail = info.thumbnail;
541
+ this.views = info.views;
542
+ this.likes = info.likes;
543
+ this.dislikes = info.dislikes;
544
+ this.reposts = info.reposts;
545
+ this.uploader = {
546
+ name: info.uploader?.name,
547
+ url: info.uploader?.url
548
+ };
549
+ this.ageRestricted = info.ageRestricted;
550
+ this.stream = { playFromSource: info.playFromSource };
551
+ this.plugin = info.plugin;
552
+ }
553
+ /**
554
+ * The playlist this song belongs to
555
+ */
556
+ get playlist() {
557
+ return __privateGet(this, _playlist);
558
+ }
559
+ set playlist(playlist) {
560
+ if (!(playlist instanceof Playlist)) throw new DisTubeError("INVALID_TYPE", "Playlist", playlist, "Song#playlist");
561
+ __privateSet(this, _playlist, playlist);
562
+ this.member = playlist.member;
563
+ }
564
+ /**
565
+ * User requested to play this song.
566
+ */
567
+ get member() {
568
+ return __privateGet(this, _member2);
569
+ }
570
+ set member(member) {
571
+ if (isMemberInstance(member)) __privateSet(this, _member2, member);
572
+ }
573
+ /**
574
+ * User requested to play this song.
575
+ */
576
+ get user() {
577
+ return this.member?.user;
578
+ }
579
+ /**
580
+ * Optional metadata that can be used to identify the song. This is attached by the
581
+ * {@link DisTube#play} method.
582
+ */
583
+ get metadata() {
584
+ return __privateGet(this, _metadata2);
585
+ }
586
+ set metadata(metadata) {
587
+ __privateSet(this, _metadata2, metadata);
588
+ }
589
+ toString() {
590
+ return this.name || this.url || this.id || "Unknown";
591
+ }
592
+ };
593
+ _metadata2 = new WeakMap();
594
+ _member2 = new WeakMap();
595
+ _playlist = new WeakMap();
596
+ __name(_Song, "Song");
597
+ var Song = _Song;
598
+
599
+ // src/core/DisTubeBase.ts
600
+ var _DisTubeBase = class _DisTubeBase {
601
+ constructor(distube) {
602
+ __publicField(this, "distube");
603
+ this.distube = distube;
604
+ }
605
+ /**
606
+ * Emit the {@link DisTube} of this base
607
+ * @param eventName - Event name
608
+ * @param args - arguments
609
+ */
610
+ emit(eventName, ...args) {
611
+ return this.distube.emit(eventName, ...args);
612
+ }
613
+ /**
614
+ * Emit error event
615
+ * @param error - error
616
+ * @param queue - The queue encountered the error
617
+ * @param song - The playing song when encountered the error
618
+ */
619
+ emitError(error, queue, song) {
620
+ this.distube.emitError(error, queue, song);
621
+ }
622
+ /**
623
+ * Emit debug event
624
+ * @param message - debug message
625
+ */
626
+ debug(message) {
627
+ this.distube.debug(message);
628
+ }
629
+ /**
630
+ * The queue manager
631
+ */
632
+ get queues() {
633
+ return this.distube.queues;
634
+ }
635
+ /**
636
+ * The voice manager
637
+ */
638
+ get voices() {
639
+ return this.distube.voices;
640
+ }
641
+ /**
642
+ * Discord.js client
643
+ */
644
+ get client() {
645
+ return this.distube.client;
646
+ }
647
+ /**
648
+ * DisTube options
649
+ */
650
+ get options() {
651
+ return this.distube.options;
652
+ }
653
+ /**
654
+ * DisTube handler
655
+ */
656
+ get handler() {
657
+ return this.distube.handler;
658
+ }
659
+ /**
660
+ * DisTube plugins
661
+ */
662
+ get plugins() {
663
+ return this.distube.plugins;
664
+ }
665
+ };
666
+ __name(_DisTubeBase, "DisTubeBase");
667
+ var DisTubeBase = _DisTubeBase;
668
+
669
+ // src/core/DisTubeVoice.ts
670
+ var import_discord = require("discord.js");
671
+ var import_tiny_typed_emitter = require("tiny-typed-emitter");
672
+ var import_voice = require("@discordjs/voice");
673
+ var _channel, _volume, _DisTubeVoice_instances, join_fn;
674
+ var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedEmitter {
675
+ constructor(voiceManager, channel) {
676
+ super();
677
+ __privateAdd(this, _DisTubeVoice_instances);
678
+ __publicField(this, "id");
679
+ __publicField(this, "voices");
680
+ __publicField(this, "audioPlayer");
681
+ __publicField(this, "connection");
682
+ __publicField(this, "emittedError");
683
+ __publicField(this, "isDisconnected", false);
684
+ __publicField(this, "stream");
685
+ __privateAdd(this, _channel);
686
+ __privateAdd(this, _volume, 100);
687
+ this.voices = voiceManager;
688
+ this.id = channel.guildId;
689
+ this.channel = channel;
690
+ this.voices.add(this.id, this);
691
+ this.audioPlayer = (0, import_voice.createAudioPlayer)().on(import_voice.AudioPlayerStatus.Idle, (oldState) => {
692
+ if (oldState.status !== import_voice.AudioPlayerStatus.Idle) this.emit("finish");
693
+ }).on("error", (error) => {
694
+ if (this.emittedError) return;
695
+ this.emittedError = true;
696
+ this.emit("error", error);
697
+ });
698
+ this.connection.on(import_voice.VoiceConnectionStatus.Disconnected, (_, newState) => {
699
+ if (newState.reason === import_voice.VoiceConnectionDisconnectReason.Manual) {
700
+ this.leave();
701
+ } else if (newState.reason === import_voice.VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
702
+ (0, import_voice.entersState)(this.connection, import_voice.VoiceConnectionStatus.Connecting, 5e3).catch(() => {
703
+ if (![import_voice.VoiceConnectionStatus.Ready, import_voice.VoiceConnectionStatus.Connecting].includes(this.connection.state.status)) {
704
+ this.leave();
705
+ }
706
+ });
707
+ } else if (this.connection.rejoinAttempts < 5) {
708
+ setTimeout(
709
+ () => {
710
+ this.connection.rejoin();
711
+ },
712
+ (this.connection.rejoinAttempts + 1) * 5e3
713
+ ).unref();
714
+ } else if (this.connection.state.status !== import_voice.VoiceConnectionStatus.Destroyed) {
715
+ this.leave(new DisTubeError("VOICE_RECONNECT_FAILED"));
716
+ }
717
+ }).on(import_voice.VoiceConnectionStatus.Destroyed, () => {
718
+ this.leave();
719
+ }).on("error", () => void 0);
720
+ this.connection.subscribe(this.audioPlayer);
721
+ }
722
+ /**
723
+ * The voice channel id the bot is in
724
+ */
725
+ get channelId() {
726
+ return this.connection?.joinConfig?.channelId ?? void 0;
727
+ }
728
+ get channel() {
729
+ if (!this.channelId) return __privateGet(this, _channel);
730
+ if (__privateGet(this, _channel)?.id === this.channelId) return __privateGet(this, _channel);
731
+ const channel = this.voices.client.channels.cache.get(this.channelId);
732
+ if (!channel) return __privateGet(this, _channel);
733
+ for (const type of import_discord.Constants.VoiceBasedChannelTypes) {
734
+ if (channel.type === type) {
735
+ __privateSet(this, _channel, channel);
736
+ return channel;
737
+ }
738
+ }
739
+ return __privateGet(this, _channel);
740
+ }
741
+ set channel(channel) {
742
+ if (!isSupportedVoiceChannel(channel)) {
743
+ throw new DisTubeError("INVALID_TYPE", "BaseGuildVoiceChannel", channel, "DisTubeVoice#channel");
744
+ }
745
+ if (channel.guildId !== this.id) throw new DisTubeError("VOICE_DIFFERENT_GUILD");
746
+ if (channel.client.user?.id !== this.voices.client.user?.id) throw new DisTubeError("VOICE_DIFFERENT_CLIENT");
747
+ if (channel.id === this.channelId) return;
748
+ if (!channel.joinable) {
749
+ if (channel.full) throw new DisTubeError("VOICE_FULL");
750
+ else throw new DisTubeError("VOICE_MISSING_PERMS");
751
+ }
752
+ this.connection = __privateMethod(this, _DisTubeVoice_instances, join_fn).call(this, channel);
753
+ __privateSet(this, _channel, channel);
754
+ }
755
+ /**
756
+ * Join a voice channel with this connection
757
+ * @param channel - A voice channel
758
+ */
759
+ async join(channel) {
760
+ const TIMEOUT = 3e4;
761
+ if (channel) this.channel = channel;
762
+ try {
763
+ await (0, import_voice.entersState)(this.connection, import_voice.VoiceConnectionStatus.Ready, TIMEOUT);
764
+ } catch {
765
+ if (this.connection.state.status === import_voice.VoiceConnectionStatus.Ready) return this;
766
+ if (this.connection.state.status !== import_voice.VoiceConnectionStatus.Destroyed) this.connection.destroy();
767
+ this.voices.remove(this.id);
768
+ throw new DisTubeError("VOICE_CONNECT_FAILED", TIMEOUT / 1e3);
769
+ }
770
+ return this;
771
+ }
772
+ /**
773
+ * Leave the voice channel of this connection
774
+ * @param error - Optional, an error to emit with 'error' event.
775
+ */
776
+ leave(error) {
777
+ this.stop(true);
778
+ if (!this.isDisconnected) {
779
+ this.emit("disconnect", error);
780
+ this.isDisconnected = true;
781
+ }
782
+ if (this.connection.state.status !== import_voice.VoiceConnectionStatus.Destroyed) this.connection.destroy();
783
+ this.voices.remove(this.id);
784
+ }
785
+ /**
786
+ * Stop the playing stream
787
+ * @param force - If true, will force the {@link DisTubeVoice#audioPlayer} to enter the Idle state even
788
+ * if the {@link DisTubeStream#audioResource} has silence padding frames.
789
+ */
790
+ stop(force = false) {
791
+ this.audioPlayer.stop(force);
792
+ }
793
+ /**
794
+ * Play a {@link DisTubeStream}
795
+ * @param dtStream - DisTubeStream
796
+ */
797
+ play(dtStream) {
798
+ this.emittedError = false;
799
+ dtStream.on("error", (error) => {
800
+ if (this.emittedError || error.code === "ERR_STREAM_PREMATURE_CLOSE") return;
801
+ this.emittedError = true;
802
+ this.emit("error", error);
803
+ });
804
+ if (this.audioPlayer.state.status !== import_voice.AudioPlayerStatus.Paused) this.audioPlayer.play(dtStream.audioResource);
805
+ this.stream?.kill();
806
+ this.stream = dtStream;
807
+ this.volume = __privateGet(this, _volume);
808
+ dtStream.spawn();
809
+ }
810
+ set volume(volume) {
811
+ if (typeof volume !== "number" || isNaN(volume)) {
812
+ throw new DisTubeError("INVALID_TYPE", "number", volume, "volume");
813
+ }
814
+ if (volume < 0) {
815
+ throw new DisTubeError("NUMBER_COMPARE", "Volume", "bigger or equal to", 0);
816
+ }
817
+ __privateSet(this, _volume, volume);
818
+ this.stream?.setVolume(Math.pow(__privateGet(this, _volume) / 100, 0.5 / Math.log10(2)));
819
+ }
820
+ /**
821
+ * Get or set the volume percentage
822
+ */
823
+ get volume() {
824
+ return __privateGet(this, _volume);
825
+ }
826
+ /**
827
+ * Playback duration of the audio resource in seconds
828
+ */
829
+ get playbackDuration() {
830
+ return (this.stream?.audioResource?.playbackDuration ?? 0) / 1e3;
831
+ }
832
+ pause() {
833
+ this.audioPlayer.pause();
834
+ }
835
+ unpause() {
836
+ const state = this.audioPlayer.state;
837
+ if (state.status !== import_voice.AudioPlayerStatus.Paused) return;
838
+ if (this.stream?.audioResource && state.resource !== this.stream.audioResource) {
839
+ this.audioPlayer.play(this.stream.audioResource);
840
+ } else {
841
+ this.audioPlayer.unpause();
842
+ }
843
+ }
844
+ /**
845
+ * Whether the bot is self-deafened
846
+ */
847
+ get selfDeaf() {
848
+ return this.connection.joinConfig.selfDeaf;
849
+ }
850
+ /**
851
+ * Whether the bot is self-muted
852
+ */
853
+ get selfMute() {
854
+ return this.connection.joinConfig.selfMute;
855
+ }
856
+ /**
857
+ * Self-deafens/undeafens the bot.
858
+ * @param selfDeaf - Whether or not the bot should be self-deafened
859
+ * @returns true if the voice state was successfully updated, otherwise false
860
+ */
861
+ setSelfDeaf(selfDeaf) {
862
+ if (typeof selfDeaf !== "boolean") {
863
+ throw new DisTubeError("INVALID_TYPE", "boolean", selfDeaf, "selfDeaf");
864
+ }
865
+ return this.connection.rejoin({
866
+ ...this.connection.joinConfig,
867
+ selfDeaf
868
+ });
869
+ }
870
+ /**
871
+ * Self-mutes/unmutes the bot.
872
+ * @param selfMute - Whether or not the bot should be self-muted
873
+ * @returns true if the voice state was successfully updated, otherwise false
874
+ */
875
+ setSelfMute(selfMute) {
876
+ if (typeof selfMute !== "boolean") {
877
+ throw new DisTubeError("INVALID_TYPE", "boolean", selfMute, "selfMute");
878
+ }
879
+ return this.connection.rejoin({
880
+ ...this.connection.joinConfig,
881
+ selfMute
882
+ });
883
+ }
884
+ /**
885
+ * The voice state of this connection
886
+ */
887
+ get voiceState() {
888
+ return this.channel?.guild?.members?.me?.voice;
889
+ }
890
+ };
891
+ _channel = new WeakMap();
892
+ _volume = new WeakMap();
893
+ _DisTubeVoice_instances = new WeakSet();
894
+ join_fn = /* @__PURE__ */ __name(function(channel) {
895
+ return (0, import_voice.joinVoiceChannel)({
896
+ channelId: channel.id,
897
+ guildId: this.id,
898
+ adapterCreator: channel.guild.voiceAdapterCreator,
899
+ group: channel.client.user?.id
900
+ });
901
+ }, "#join");
902
+ __name(_DisTubeVoice, "DisTubeVoice");
903
+ var DisTubeVoice = _DisTubeVoice;
904
+
905
+ // src/core/DisTubeStream.ts
906
+ var import_stream = require("stream");
907
+ var import_child_process = require("child_process");
908
+ var import_tiny_typed_emitter2 = require("tiny-typed-emitter");
909
+ var import_voice2 = require("@discordjs/voice");
910
+ var checked = process.env.NODE_ENV === "test";
911
+ var checkFFmpeg = /* @__PURE__ */ __name((distube) => {
912
+ if (checked) return;
913
+ const path = distube.options.ffmpeg.path;
914
+ const debug = /* @__PURE__ */ __name((str) => distube.emit("ffmpegDebug" /* FFMPEG_DEBUG */, str), "debug");
915
+ try {
916
+ debug(`[test] spawn ffmpeg at '${path}' path`);
917
+ const process2 = (0, import_child_process.spawnSync)(path, ["-h"], { windowsHide: true, shell: true, encoding: "utf-8" });
918
+ if (process2.error) throw process2.error;
919
+ if (process2.stderr && !process2.stdout) throw new Error(process2.stderr);
920
+ const result = process2.output.join("\n");
921
+ const version2 = /ffmpeg version (\S+)/iu.exec(result)?.[1];
922
+ if (!version2) throw new Error("Invalid FFmpeg version");
923
+ debug(`[test] ffmpeg version: ${version2}`);
924
+ } catch (e) {
925
+ debug(`[test] failed to spawn ffmpeg at '${path}': ${e?.stack ?? e}`);
926
+ throw new DisTubeError("FFMPEG_NOT_INSTALLED", path);
927
+ }
928
+ checked = true;
929
+ }, "checkFFmpeg");
930
+ var _ffmpegPath, _opts;
931
+ var _DisTubeStream = class _DisTubeStream extends import_tiny_typed_emitter2.TypedEmitter {
932
+ /**
933
+ * Create a DisTubeStream to play with {@link DisTubeVoice}
934
+ * @param url - Stream URL
935
+ * @param options - Stream options
936
+ */
937
+ constructor(url, options) {
938
+ super();
939
+ __privateAdd(this, _ffmpegPath);
940
+ __privateAdd(this, _opts);
941
+ __publicField(this, "process");
942
+ __publicField(this, "stream");
943
+ __publicField(this, "audioResource");
944
+ const { ffmpeg, seek } = options;
945
+ const opts = {
946
+ reconnect: 1,
947
+ reconnect_streamed: 1,
948
+ reconnect_delay_max: 5,
949
+ analyzeduration: 0,
950
+ hide_banner: true,
951
+ ...ffmpeg.args.global,
952
+ ...ffmpeg.args.input,
953
+ i: url,
954
+ ar: 48e3,
955
+ ac: 2,
956
+ ...ffmpeg.args.output,
957
+ f: "s16le"
958
+ };
959
+ if (typeof seek === "number" && seek > 0) opts.ss = seek.toString();
960
+ const fileUrl = new URL(url);
961
+ if (fileUrl.protocol === "file:") {
962
+ opts.reconnect = null;
963
+ opts.reconnect_streamed = null;
964
+ opts.reconnect_delay_max = null;
965
+ opts.i = fileUrl.hostname + fileUrl.pathname;
966
+ }
967
+ __privateSet(this, _ffmpegPath, ffmpeg.path);
968
+ __privateSet(this, _opts, [
969
+ ...Object.entries(opts).flatMap(
970
+ ([key, value]) => Array.isArray(value) ? value.filter(Boolean).map((v) => [`-${key}`, String(v)]) : value == null || value === false ? [] : [value === true ? `-${key}` : [`-${key}`, String(value)]]
971
+ ).flat(),
972
+ "pipe:1"
973
+ ]);
974
+ this.stream = new VolumeTransformer();
975
+ this.stream.on("close", () => this.kill()).on("error", (err) => {
976
+ this.debug(`[stream] error: ${err.message}`);
977
+ this.emit("error", err);
978
+ }).on("finish", () => this.debug("[stream] log: stream finished"));
979
+ this.audioResource = (0, import_voice2.createAudioResource)(this.stream, { inputType: import_voice2.StreamType.Raw, inlineVolume: false });
980
+ }
981
+ spawn() {
982
+ this.debug(`[process] spawn: ${__privateGet(this, _ffmpegPath)} ${__privateGet(this, _opts).join(" ")}`);
983
+ this.process = (0, import_child_process.spawn)(__privateGet(this, _ffmpegPath), __privateGet(this, _opts), {
984
+ stdio: ["ignore", "pipe", "pipe"],
985
+ shell: false,
986
+ windowsHide: true
987
+ }).on("error", (err) => {
988
+ this.debug(`[process] error: ${err.message}`);
989
+ this.emit("error", err);
990
+ }).on("exit", (code, signal) => {
991
+ this.debug(`[process] exit: code=${code ?? "unknown"} signal=${signal ?? "unknown"}`);
992
+ if (!code || [0, 255].includes(code)) return;
993
+ this.debug(`[process] error: ffmpeg exited with code ${code}`);
994
+ this.emit("error", new DisTubeError("FFMPEG_EXITED", code));
995
+ });
996
+ if (!this.process.stdout || !this.process.stderr) {
997
+ this.kill();
998
+ throw new Error("Failed to create ffmpeg process");
999
+ }
1000
+ this.process.stdout.pipe(this.stream);
1001
+ this.process.stderr.setEncoding("utf8")?.on("data", (data) => {
1002
+ const lines = data.split(/\r\n|\r|\n/u);
1003
+ for (const line of lines) {
1004
+ if (/^\s*$/.test(line)) continue;
1005
+ this.debug(`[ffmpeg] log: ${line}`);
1006
+ }
1007
+ });
1008
+ }
1009
+ debug(debug) {
1010
+ this.emit("debug", debug);
1011
+ }
1012
+ setVolume(volume) {
1013
+ this.stream.vol = volume;
1014
+ }
1015
+ kill() {
1016
+ if (!this.stream.destroyed) this.stream.destroy();
1017
+ if (this.process && !this.process.killed) this.process.kill("SIGKILL");
1018
+ }
1019
+ };
1020
+ _ffmpegPath = new WeakMap();
1021
+ _opts = new WeakMap();
1022
+ __name(_DisTubeStream, "DisTubeStream");
1023
+ var DisTubeStream = _DisTubeStream;
1024
+ var _VolumeTransformer = class _VolumeTransformer extends import_stream.Transform {
1025
+ constructor() {
1026
+ super(...arguments);
1027
+ __publicField(this, "buffer", Buffer.allocUnsafe(0));
1028
+ __publicField(this, "extrema", [-Math.pow(2, 16 - 1), Math.pow(2, 16 - 1) - 1]);
1029
+ __publicField(this, "vol", 1);
1030
+ }
1031
+ _transform(newChunk, _encoding, done) {
1032
+ const { vol } = this;
1033
+ if (vol === 1) {
1034
+ this.push(newChunk);
1035
+ done();
1036
+ return;
1037
+ }
1038
+ const bytes = 2;
1039
+ const chunk = Buffer.concat([this.buffer, newChunk]);
1040
+ const readableLength = Math.floor(chunk.length / bytes) * bytes;
1041
+ for (let i = 0; i < readableLength; i += bytes) {
1042
+ const value = chunk.readInt16LE(i);
1043
+ const clampedValue = Math.min(this.extrema[1], Math.max(this.extrema[0], value * vol));
1044
+ chunk.writeInt16LE(clampedValue, i);
1045
+ }
1046
+ this.buffer = chunk.subarray(readableLength);
1047
+ this.push(chunk.subarray(0, readableLength));
1048
+ done();
1049
+ }
1050
+ };
1051
+ __name(_VolumeTransformer, "VolumeTransformer");
1052
+ var VolumeTransformer = _VolumeTransformer;
1053
+
1054
+ // src/core/DisTubeHandler.ts
1055
+ var import_undici = require("undici");
1056
+ var REDIRECT_CODES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
1057
+ var _DisTubeHandler_instances, searchSong_fn;
1058
+ var _DisTubeHandler = class _DisTubeHandler extends DisTubeBase {
1059
+ constructor() {
1060
+ super(...arguments);
1061
+ __privateAdd(this, _DisTubeHandler_instances);
1062
+ }
1063
+ /**
1064
+ * Resolve a url or a supported object to a {@link Song} or {@link Playlist}
1065
+ * @throws {@link DisTubeError}
1066
+ * @param input - Resolvable input
1067
+ * @param options - Optional options
1068
+ * @returns Resolved
1069
+ */
1070
+ async resolve(input, options = {}) {
1071
+ if (input instanceof Song || input instanceof Playlist) {
1072
+ if ("metadata" in options) input.metadata = options.metadata;
1073
+ if ("member" in options) input.member = options.member;
1074
+ return input;
1075
+ }
1076
+ if (typeof input === "string") {
1077
+ if (isURL(input)) {
1078
+ const plugin = await this._getPluginFromURL(input) || await this._getPluginFromURL(await this.followRedirectLink(input));
1079
+ if (!plugin) throw new DisTubeError("NOT_SUPPORTED_URL");
1080
+ this.debug(`[${plugin.constructor.name}] Resolving from url: ${input}`);
1081
+ return plugin.resolve(input, options);
1082
+ }
1083
+ try {
1084
+ const song = await __privateMethod(this, _DisTubeHandler_instances, searchSong_fn).call(this, input, options);
1085
+ if (song) return song;
1086
+ } catch {
1087
+ }
1088
+ }
1089
+ throw new DisTubeError("CANNOT_RESOLVE_SONG", input);
1090
+ }
1091
+ async _getPluginFromURL(url) {
1092
+ for (const plugin of this.plugins) if (await plugin.validate(url)) return plugin;
1093
+ return null;
1094
+ }
1095
+ async _getPluginFromSong(song, types, validate = true) {
1096
+ if (!types || types.includes(song.plugin?.type)) return song.plugin;
1097
+ if (!song.url) return null;
1098
+ for (const plugin of this.plugins) {
1099
+ if ((!types || types.includes(plugin?.type)) && (!validate || await plugin.validate(song.url))) {
1100
+ return plugin;
1101
+ }
1102
+ }
1103
+ return null;
1104
+ }
1105
+ /**
1106
+ * Get {@link Song}'s stream info and attach it to the song.
1107
+ * @param song - A Song
1108
+ */
1109
+ async attachStreamInfo(song) {
1110
+ if (song.stream.playFromSource) {
1111
+ if (song.stream.url) return;
1112
+ this.debug(`[DisTubeHandler] Getting stream info: ${song}`);
1113
+ const plugin = await this._getPluginFromSong(song, ["extractor" /* EXTRACTOR */, "playable-extractor" /* PLAYABLE_EXTRACTOR */]);
1114
+ if (!plugin) throw new DisTubeError("NOT_SUPPORTED_SONG", song.toString());
1115
+ this.debug(`[${plugin.constructor.name}] Getting stream URL: ${song}`);
1116
+ song.stream.url = await plugin.getStreamURL(song);
1117
+ if (!song.stream.url) throw new DisTubeError("CANNOT_GET_STREAM_URL", song.toString());
1118
+ } else {
1119
+ if (song.stream.song?.stream?.playFromSource && song.stream.song.stream.url) return;
1120
+ this.debug(`[DisTubeHandler] Getting stream info: ${song}`);
1121
+ const plugin = await this._getPluginFromSong(song, ["info-extractor" /* INFO_EXTRACTOR */]);
1122
+ if (!plugin) throw new DisTubeError("NOT_SUPPORTED_SONG", song.toString());
1123
+ this.debug(`[${plugin.constructor.name}] Creating search query for: ${song}`);
1124
+ const query = await plugin.createSearchQuery(song);
1125
+ if (!query) throw new DisTubeError("CANNOT_GET_SEARCH_QUERY", song.toString());
1126
+ const altSong = await __privateMethod(this, _DisTubeHandler_instances, searchSong_fn).call(this, query, { metadata: song.metadata, member: song.member }, true);
1127
+ if (!altSong || !altSong.stream.playFromSource) throw new DisTubeError("NO_RESULT", query || song.toString());
1128
+ song.stream.song = altSong;
1129
+ }
1130
+ }
1131
+ async followRedirectLink(url, maxRedirect = 5) {
1132
+ if (maxRedirect === 0) return url;
1133
+ const res = await (0, import_undici.request)(url, {
1134
+ method: "HEAD",
1135
+ headers: {
1136
+ "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"
1137
+ }
1138
+ });
1139
+ if (REDIRECT_CODES.has(res.statusCode ?? 200)) {
1140
+ let location = res.headers.location;
1141
+ if (typeof location !== "string") location = location?.[0] ?? url;
1142
+ return this.followRedirectLink(location, --maxRedirect);
1143
+ }
1144
+ return url;
1145
+ }
1146
+ };
1147
+ _DisTubeHandler_instances = new WeakSet();
1148
+ searchSong_fn = /* @__PURE__ */ __name(async function(query, options = {}, getStreamURL = false) {
1149
+ for (const plugin of this.plugins) {
1150
+ if (plugin.type === "extractor" /* EXTRACTOR */) {
1151
+ this.debug(`[${plugin.constructor.name}] Searching for song: ${query}`);
1152
+ const result = await plugin.searchSong(query, options);
1153
+ if (result) {
1154
+ if (getStreamURL && result.stream.playFromSource) result.stream.url = await plugin.getStreamURL(result);
1155
+ return result;
1156
+ }
1157
+ }
1158
+ }
1159
+ return null;
1160
+ }, "#searchSong");
1161
+ __name(_DisTubeHandler, "DisTubeHandler");
1162
+ var DisTubeHandler = _DisTubeHandler;
1163
+
1164
+ // src/core/DisTubeOptions.ts
1165
+ var _Options_instances, validateOptions_fn, ffmpegOption_fn;
1166
+ var _Options = class _Options {
1167
+ constructor(options) {
1168
+ __privateAdd(this, _Options_instances);
1169
+ __publicField(this, "plugins");
1170
+ __publicField(this, "emitNewSongOnly");
1171
+ __publicField(this, "savePreviousSongs");
1172
+ __publicField(this, "customFilters");
1173
+ __publicField(this, "nsfw");
1174
+ __publicField(this, "emitAddSongWhenCreatingQueue");
1175
+ __publicField(this, "emitAddListWhenCreatingQueue");
1176
+ __publicField(this, "joinNewVoiceChannel");
1177
+ __publicField(this, "ffmpeg");
1178
+ if (typeof options !== "object" || Array.isArray(options)) {
1179
+ throw new DisTubeError("INVALID_TYPE", "object", options, "DisTubeOptions");
1180
+ }
1181
+ const opts = { ...defaultOptions, ...options };
1182
+ this.plugins = opts.plugins;
1183
+ this.emitNewSongOnly = opts.emitNewSongOnly;
1184
+ this.savePreviousSongs = opts.savePreviousSongs;
1185
+ this.customFilters = opts.customFilters;
1186
+ this.nsfw = opts.nsfw;
1187
+ this.emitAddSongWhenCreatingQueue = opts.emitAddSongWhenCreatingQueue;
1188
+ this.emitAddListWhenCreatingQueue = opts.emitAddListWhenCreatingQueue;
1189
+ this.joinNewVoiceChannel = opts.joinNewVoiceChannel;
1190
+ this.ffmpeg = __privateMethod(this, _Options_instances, ffmpegOption_fn).call(this, options);
1191
+ checkInvalidKey(opts, this, "DisTubeOptions");
1192
+ __privateMethod(this, _Options_instances, validateOptions_fn).call(this);
1193
+ }
1194
+ };
1195
+ _Options_instances = new WeakSet();
1196
+ validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
1197
+ const booleanOptions = /* @__PURE__ */ new Set([
1198
+ "emitNewSongOnly",
1199
+ "savePreviousSongs",
1200
+ "joinNewVoiceChannel",
1201
+ "nsfw",
1202
+ "emitAddSongWhenCreatingQueue",
1203
+ "emitAddListWhenCreatingQueue"
1204
+ ]);
1205
+ const numberOptions = /* @__PURE__ */ new Set();
1206
+ const stringOptions = /* @__PURE__ */ new Set();
1207
+ const objectOptions = /* @__PURE__ */ new Set(["customFilters", "ffmpeg"]);
1208
+ const optionalOptions = /* @__PURE__ */ new Set(["customFilters"]);
1209
+ for (const [key, value] of Object.entries(options)) {
1210
+ if (value === void 0 && optionalOptions.has(key)) continue;
1211
+ if (key === "plugins" && !Array.isArray(value)) {
1212
+ throw new DisTubeError("INVALID_TYPE", "Array<Plugin>", value, `DisTubeOptions.${key}`);
1213
+ } else if (booleanOptions.has(key)) {
1214
+ if (typeof value !== "boolean") {
1215
+ throw new DisTubeError("INVALID_TYPE", "boolean", value, `DisTubeOptions.${key}`);
1216
+ }
1217
+ } else if (numberOptions.has(key)) {
1218
+ if (typeof value !== "number" || isNaN(value)) {
1219
+ throw new DisTubeError("INVALID_TYPE", "number", value, `DisTubeOptions.${key}`);
1220
+ }
1221
+ } else if (stringOptions.has(key)) {
1222
+ if (typeof value !== "string") {
1223
+ throw new DisTubeError("INVALID_TYPE", "string", value, `DisTubeOptions.${key}`);
1224
+ }
1225
+ } else if (objectOptions.has(key)) {
1226
+ if (typeof value !== "object" || Array.isArray(value)) {
1227
+ throw new DisTubeError("INVALID_TYPE", "object", value, `DisTubeOptions.${key}`);
1228
+ }
1229
+ }
1230
+ }
1231
+ }, "#validateOptions");
1232
+ ffmpegOption_fn = /* @__PURE__ */ __name(function(opts) {
1233
+ const args = { global: {}, input: {}, output: {} };
1234
+ if (opts.ffmpeg?.args) {
1235
+ if (opts.ffmpeg.args.global) args.global = opts.ffmpeg.args.global;
1236
+ if (opts.ffmpeg.args.input) args.input = opts.ffmpeg.args.input;
1237
+ if (opts.ffmpeg.args.output) args.output = opts.ffmpeg.args.output;
1238
+ }
1239
+ const path = opts.ffmpeg?.path ?? "ffmpeg";
1240
+ if (typeof path !== "string") {
1241
+ throw new DisTubeError("INVALID_TYPE", "string", path, "DisTubeOptions.ffmpeg.path");
1242
+ }
1243
+ for (const [key, value] of Object.entries(args)) {
1244
+ if (typeof value !== "object" || Array.isArray(value)) {
1245
+ throw new DisTubeError("INVALID_TYPE", "object", value, `DisTubeOptions.ffmpeg.${key}`);
1246
+ }
1247
+ for (const [k, v] of Object.entries(value)) {
1248
+ if (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean" && !Array.isArray(v) && v !== null && v !== void 0) {
1249
+ throw new DisTubeError(
1250
+ "INVALID_TYPE",
1251
+ ["string", "number", "boolean", "Array<string | null | undefined>", "null", "undefined"],
1252
+ v,
1253
+ `DisTubeOptions.ffmpeg.${key}.${k}`
1254
+ );
1255
+ }
1256
+ }
1257
+ }
1258
+ return { path, args };
1259
+ }, "#ffmpegOption");
1260
+ __name(_Options, "Options");
1261
+ var Options = _Options;
1262
+
1263
+ // src/core/manager/BaseManager.ts
1264
+ var import_discord2 = require("discord.js");
1265
+ var _BaseManager = class _BaseManager extends DisTubeBase {
1266
+ constructor() {
1267
+ super(...arguments);
1268
+ /**
1269
+ * The collection of items for this manager.
1270
+ */
1271
+ __publicField(this, "collection", new import_discord2.Collection());
1272
+ }
1273
+ /**
1274
+ * The size of the collection.
1275
+ */
1276
+ get size() {
1277
+ return this.collection.size;
1278
+ }
1279
+ };
1280
+ __name(_BaseManager, "BaseManager");
1281
+ var BaseManager = _BaseManager;
1282
+
1283
+ // src/core/manager/GuildIdManager.ts
1284
+ var _GuildIdManager = class _GuildIdManager extends BaseManager {
1285
+ add(idOrInstance, data) {
1286
+ const id = resolveGuildId(idOrInstance);
1287
+ const existing = this.get(id);
1288
+ if (existing) return this;
1289
+ this.collection.set(id, data);
1290
+ return this;
1291
+ }
1292
+ get(idOrInstance) {
1293
+ return this.collection.get(resolveGuildId(idOrInstance));
1294
+ }
1295
+ remove(idOrInstance) {
1296
+ return this.collection.delete(resolveGuildId(idOrInstance));
1297
+ }
1298
+ has(idOrInstance) {
1299
+ return this.collection.has(resolveGuildId(idOrInstance));
1300
+ }
1301
+ };
1302
+ __name(_GuildIdManager, "GuildIdManager");
1303
+ var GuildIdManager = _GuildIdManager;
1304
+
1305
+ // src/core/manager/DisTubeVoiceManager.ts
1306
+ var import_voice3 = require("@discordjs/voice");
1307
+ var _DisTubeVoiceManager = class _DisTubeVoiceManager extends GuildIdManager {
1308
+ /**
1309
+ * Create a {@link DisTubeVoice} instance
1310
+ * @param channel - A voice chann el to join
1311
+ */
1312
+ create(channel) {
1313
+ const existing = this.get(channel.guildId);
1314
+ if (existing) {
1315
+ existing.channel = channel;
1316
+ return existing;
1317
+ }
1318
+ if ((0, import_voice3.getVoiceConnection)(resolveGuildId(channel), this.client.user?.id) || (0, import_voice3.getVoiceConnection)(resolveGuildId(channel))) {
1319
+ throw new DisTubeError("VOICE_ALREADY_CREATED");
1320
+ }
1321
+ return new DisTubeVoice(this, channel);
1322
+ }
1323
+ /**
1324
+ * Join a voice channel and wait until the connection is ready
1325
+ * @param channel - A voice channel to join
1326
+ */
1327
+ join(channel) {
1328
+ const existing = this.get(channel.guildId);
1329
+ if (existing) return existing.join(channel);
1330
+ return this.create(channel).join();
1331
+ }
1332
+ /**
1333
+ * Leave the connected voice channel in a guild
1334
+ * @param guild - Queue Resolvable
1335
+ */
1336
+ leave(guild) {
1337
+ const voice = this.get(guild);
1338
+ if (voice) {
1339
+ voice.leave();
1340
+ } else {
1341
+ const connection = (0, import_voice3.getVoiceConnection)(resolveGuildId(guild), this.client.user?.id) ?? (0, import_voice3.getVoiceConnection)(resolveGuildId(guild));
1342
+ if (connection && connection.state.status !== import_voice3.VoiceConnectionStatus.Destroyed) {
1343
+ connection.destroy();
1344
+ }
1345
+ }
1346
+ }
1347
+ };
1348
+ __name(_DisTubeVoiceManager, "DisTubeVoiceManager");
1349
+ var DisTubeVoiceManager = _DisTubeVoiceManager;
1350
+
1351
+ // src/core/manager/FilterManager.ts
1352
+ var _FilterManager_instances, resolve_fn, apply_fn, removeFn_fn;
1353
+ var _FilterManager = class _FilterManager extends BaseManager {
1354
+ constructor(queue) {
1355
+ super(queue.distube);
1356
+ __privateAdd(this, _FilterManager_instances);
1357
+ /**
1358
+ * The queue to manage
1359
+ */
1360
+ __publicField(this, "queue");
1361
+ this.queue = queue;
1362
+ }
1363
+ /**
1364
+ * Enable a filter or multiple filters to the manager
1365
+ * @param filterOrFilters - The filter or filters to enable
1366
+ * @param override - Wether or not override the applied filter with new filter value
1367
+ */
1368
+ add(filterOrFilters, override = false) {
1369
+ if (Array.isArray(filterOrFilters)) {
1370
+ for (const filter of filterOrFilters) {
1371
+ const ft = __privateMethod(this, _FilterManager_instances, resolve_fn).call(this, filter);
1372
+ if (override || !this.has(ft)) this.collection.set(ft.name, ft);
1373
+ }
1374
+ } else {
1375
+ const ft = __privateMethod(this, _FilterManager_instances, resolve_fn).call(this, filterOrFilters);
1376
+ if (override || !this.has(ft)) this.collection.set(ft.name, ft);
1377
+ }
1378
+ __privateMethod(this, _FilterManager_instances, apply_fn).call(this);
1379
+ return this;
1380
+ }
1381
+ /**
1382
+ * Clear enabled filters of the manager
1383
+ */
1384
+ clear() {
1385
+ return this.set([]);
1386
+ }
1387
+ /**
1388
+ * Set the filters applied to the manager
1389
+ * @param filters - The filters to apply
1390
+ */
1391
+ set(filters) {
1392
+ if (!Array.isArray(filters)) throw new DisTubeError("INVALID_TYPE", "Array<FilterResolvable>", filters, "filters");
1393
+ this.collection.clear();
1394
+ for (const f of filters) {
1395
+ const filter = __privateMethod(this, _FilterManager_instances, resolve_fn).call(this, f);
1396
+ this.collection.set(filter.name, filter);
1397
+ }
1398
+ __privateMethod(this, _FilterManager_instances, apply_fn).call(this);
1399
+ return this;
1400
+ }
1401
+ /**
1402
+ * Disable a filter or multiple filters
1403
+ * @param filterOrFilters - The filter or filters to disable
1404
+ */
1405
+ remove(filterOrFilters) {
1406
+ if (Array.isArray(filterOrFilters)) filterOrFilters.forEach((f) => __privateMethod(this, _FilterManager_instances, removeFn_fn).call(this, f));
1407
+ else __privateMethod(this, _FilterManager_instances, removeFn_fn).call(this, filterOrFilters);
1408
+ __privateMethod(this, _FilterManager_instances, apply_fn).call(this);
1409
+ return this;
1410
+ }
1411
+ /**
1412
+ * Check whether a filter enabled or not
1413
+ * @param filter - The filter to check
1414
+ */
1415
+ has(filter) {
1416
+ return this.collection.has(typeof filter === "string" ? filter : __privateMethod(this, _FilterManager_instances, resolve_fn).call(this, filter).name);
1417
+ }
1418
+ /**
1419
+ * Array of enabled filter names
1420
+ */
1421
+ get names() {
1422
+ return [...this.collection.keys()];
1423
+ }
1424
+ /**
1425
+ * Array of enabled filters
1426
+ */
1427
+ get values() {
1428
+ return [...this.collection.values()];
1429
+ }
1430
+ get ffmpegArgs() {
1431
+ return this.size ? { af: this.values.map((f) => f.value).join(",") } : {};
1432
+ }
1433
+ toString() {
1434
+ return this.names.toString();
1435
+ }
1436
+ };
1437
+ _FilterManager_instances = new WeakSet();
1438
+ resolve_fn = /* @__PURE__ */ __name(function(filter) {
1439
+ if (typeof filter === "object" && typeof filter.name === "string" && typeof filter.value === "string") {
1440
+ return filter;
1441
+ }
1442
+ if (typeof filter === "string" && Object.prototype.hasOwnProperty.call(this.distube.filters, filter)) {
1443
+ return {
1444
+ name: filter,
1445
+ value: this.distube.filters[filter]
1446
+ };
1447
+ }
1448
+ throw new DisTubeError("INVALID_TYPE", "FilterResolvable", filter, "filter");
1449
+ }, "#resolve");
1450
+ apply_fn = /* @__PURE__ */ __name(function() {
1451
+ this.queue._beginTime = this.queue.currentTime;
1452
+ this.queue.play(false);
1453
+ }, "#apply");
1454
+ removeFn_fn = /* @__PURE__ */ __name(function(f) {
1455
+ return this.collection.delete(__privateMethod(this, _FilterManager_instances, resolve_fn).call(this, f).name);
1456
+ }, "#removeFn");
1457
+ __name(_FilterManager, "FilterManager");
1458
+ var FilterManager = _FilterManager;
1459
+
1460
+ // src/core/manager/QueueManager.ts
1461
+ var _QueueManager_instances, voiceEventHandler_fn, emitPlaySong_fn, handleSongFinish_fn, handlePlayingError_fn;
1462
+ var _QueueManager = class _QueueManager extends GuildIdManager {
1463
+ constructor() {
1464
+ super(...arguments);
1465
+ __privateAdd(this, _QueueManager_instances);
1466
+ }
1467
+ /**
1468
+ * Create a {@link Queue}
1469
+ * @param channel - A voice channel
1470
+ * @param textChannel - Default text channel
1471
+ * @returns Returns `true` if encounter an error
1472
+ */
1473
+ async create(channel, textChannel) {
1474
+ if (this.has(channel.guildId)) throw new DisTubeError("QUEUE_EXIST");
1475
+ this.debug(`[QueueManager] Creating queue for guild: ${channel.guildId}`);
1476
+ const voice = this.voices.create(channel);
1477
+ const queue = new Queue(this.distube, voice, textChannel);
1478
+ await queue._taskQueue.queuing();
1479
+ try {
1480
+ checkFFmpeg(this.distube);
1481
+ this.debug(`[QueueManager] Joining voice channel: ${channel.id}`);
1482
+ await voice.join();
1483
+ __privateMethod(this, _QueueManager_instances, voiceEventHandler_fn).call(this, queue);
1484
+ this.add(queue.id, queue);
1485
+ this.emit("initQueue" /* INIT_QUEUE */, queue);
1486
+ return queue;
1487
+ } finally {
1488
+ queue._taskQueue.resolve();
1489
+ }
1490
+ }
1491
+ /**
1492
+ * Play a song on voice connection with queue properties
1493
+ * @param queue - The guild queue to play
1494
+ * @param emitPlaySong - Whether or not emit {@link Events.PLAY_SONG} event
1495
+ */
1496
+ async playSong(queue, emitPlaySong = true) {
1497
+ if (!queue) return;
1498
+ if (queue.stopped || !queue.songs.length) {
1499
+ queue.stop();
1500
+ return;
1501
+ }
1502
+ try {
1503
+ const song = queue.songs[0];
1504
+ this.debug(`[${queue.id}] Getting stream from: ${song}`);
1505
+ await this.handler.attachStreamInfo(song);
1506
+ const willPlaySong = song.stream.playFromSource ? song : song.stream.song;
1507
+ const stream = willPlaySong?.stream;
1508
+ if (!willPlaySong || !stream?.playFromSource || !stream.url) throw new DisTubeError("NO_STREAM_URL", `${song}`);
1509
+ this.debug(`[${queue.id}] Creating DisTubeStream for: ${willPlaySong}`);
1510
+ const streamOptions = {
1511
+ ffmpeg: {
1512
+ path: this.options.ffmpeg.path,
1513
+ args: {
1514
+ global: { ...queue.ffmpegArgs.global },
1515
+ input: { ...queue.ffmpegArgs.input },
1516
+ output: { ...queue.ffmpegArgs.output, ...queue.filters.ffmpegArgs }
1517
+ }
1518
+ },
1519
+ seek: willPlaySong.duration ? queue._beginTime : void 0
1520
+ };
1521
+ const dtStream = new DisTubeStream(stream.url, streamOptions);
1522
+ dtStream.on("debug", (data) => this.emit("ffmpegDebug" /* FFMPEG_DEBUG */, `[${queue.id}] ${data}`));
1523
+ this.debug(`[${queue.id}] Started playing: ${willPlaySong}`);
1524
+ queue.voice.play(dtStream);
1525
+ if (emitPlaySong) this.emit("playSong" /* PLAY_SONG */, queue, song);
1526
+ } catch (e) {
1527
+ __privateMethod(this, _QueueManager_instances, handlePlayingError_fn).call(this, queue, e);
1528
+ }
1529
+ }
1530
+ };
1531
+ _QueueManager_instances = new WeakSet();
1532
+ /**
1533
+ * Listen to DisTubeVoice events and handle the Queue
1534
+ * @param queue - Queue
1535
+ */
1536
+ voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
1537
+ queue._listeners = {
1538
+ disconnect: /* @__PURE__ */ __name((error) => {
1539
+ queue.remove();
1540
+ this.emit("disconnect" /* DISCONNECT */, queue);
1541
+ if (error) this.emitError(error, queue, queue.songs?.[0]);
1542
+ }, "disconnect"),
1543
+ error: /* @__PURE__ */ __name((error) => __privateMethod(this, _QueueManager_instances, handlePlayingError_fn).call(this, queue, error), "error"),
1544
+ finish: /* @__PURE__ */ __name(() => __privateMethod(this, _QueueManager_instances, handleSongFinish_fn).call(this, queue), "finish")
1545
+ };
1546
+ for (const event of objectKeys(queue._listeners)) {
1547
+ queue.voice.on(event, queue._listeners[event]);
1548
+ }
1549
+ }, "#voiceEventHandler");
1550
+ /**
1551
+ * Whether or not emit playSong event
1552
+ * @param queue - Queue
1553
+ */
1554
+ emitPlaySong_fn = /* @__PURE__ */ __name(function(queue) {
1555
+ if (!this.options.emitNewSongOnly) return true;
1556
+ if (queue.repeatMode === 1 /* SONG */) return queue._next || queue._prev;
1557
+ return queue.songs[0].id !== queue.songs[1].id;
1558
+ }, "#emitPlaySong");
1559
+ handleSongFinish_fn = /* @__PURE__ */ __name(async function(queue) {
1560
+ this.debug(`[QueueManager] Handling song finish: ${queue.id}`);
1561
+ const song = queue.songs[0];
1562
+ this.emit("finishSong" /* FINISH_SONG */, queue, queue.songs[0]);
1563
+ await queue._taskQueue.queuing();
1564
+ try {
1565
+ if (queue.stopped) return;
1566
+ if (queue.repeatMode === 2 /* QUEUE */ && !queue._prev) queue.songs.push(song);
1567
+ if (queue._prev) {
1568
+ if (queue.repeatMode === 2 /* QUEUE */) queue.songs.unshift(queue.songs.pop());
1569
+ else queue.songs.unshift(queue.previousSongs.pop());
1570
+ }
1571
+ if (queue.songs.length <= 1 && (queue._next || queue.repeatMode === 0 /* DISABLED */)) {
1572
+ if (queue.autoplay) {
1573
+ try {
1574
+ this.debug(`[QueueManager] Adding related song: ${queue.id}`);
1575
+ await queue.addRelatedSong();
1576
+ } catch (e) {
1577
+ this.debug(`[${queue.id}] Add related song error: ${e.message}`);
1578
+ this.emit("noRelated" /* NO_RELATED */, queue, e);
1579
+ }
1580
+ }
1581
+ if (queue.songs.length <= 1) {
1582
+ this.debug(`[${queue.id}] Queue is empty, stopping...`);
1583
+ if (!queue.autoplay) this.emit("finish" /* FINISH */, queue);
1584
+ queue.remove();
1585
+ return;
1586
+ }
1587
+ }
1588
+ const emitPlaySong = __privateMethod(this, _QueueManager_instances, emitPlaySong_fn).call(this, queue);
1589
+ if (!queue._prev && (queue.repeatMode !== 1 /* SONG */ || queue._next)) {
1590
+ const prev = queue.songs.shift();
1591
+ if (this.options.savePreviousSongs) queue.previousSongs.push(prev);
1592
+ else queue.previousSongs.push({ id: prev.id });
1593
+ }
1594
+ queue._next = queue._prev = false;
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);
1601
+ } finally {
1602
+ queue._taskQueue.resolve();
1603
+ }
1604
+ }, "#handleSongFinish");
1605
+ /**
1606
+ * Handle error while playing
1607
+ * @param queue - queue
1608
+ * @param error - error
1609
+ */
1610
+ handlePlayingError_fn = /* @__PURE__ */ __name(function(queue, error) {
1611
+ const song = queue.songs.shift();
1612
+ try {
1613
+ error.name = "PlayingError";
1614
+ } catch {
1615
+ }
1616
+ this.debug(`[${queue.id}] Error while playing: ${error.stack || error.message}`);
1617
+ this.emitError(error, queue, song);
1618
+ if (queue.songs.length > 0) {
1619
+ this.debug(`[${queue.id}] Playing next song: ${queue.songs[0]}`);
1620
+ queue._next = queue._prev = false;
1621
+ queue._beginTime = 0;
1622
+ this.playSong(queue);
1623
+ } else {
1624
+ this.debug(`[${queue.id}] Queue is empty, stopping...`);
1625
+ queue.stop();
1626
+ }
1627
+ }, "#handlePlayingError");
1628
+ __name(_QueueManager, "QueueManager");
1629
+ var QueueManager = _QueueManager;
1630
+
1631
+ // src/struct/Queue.ts
1632
+ var _filters, _Queue_instances, getRelatedSong_fn;
1633
+ var _Queue = class _Queue extends DisTubeBase {
1634
+ /**
1635
+ * Create a queue for the guild
1636
+ * @param distube - DisTube
1637
+ * @param voice - Voice connection
1638
+ * @param textChannel - Default text channel
1639
+ */
1640
+ constructor(distube, voice, textChannel) {
1641
+ super(distube);
1642
+ __privateAdd(this, _Queue_instances);
1643
+ /**
1644
+ * Queue id (Guild id)
1645
+ */
1646
+ __publicField(this, "id");
1647
+ /**
1648
+ * Voice connection of this queue.
1649
+ */
1650
+ __publicField(this, "voice");
1651
+ /**
1652
+ * List of songs in the queue (The first one is the playing song)
1653
+ */
1654
+ __publicField(this, "songs");
1655
+ /**
1656
+ * List of the previous songs.
1657
+ */
1658
+ __publicField(this, "previousSongs");
1659
+ /**
1660
+ * Whether stream is currently stopped.
1661
+ */
1662
+ __publicField(this, "stopped");
1663
+ /**
1664
+ * Whether or not the stream is currently playing.
1665
+ */
1666
+ __publicField(this, "playing");
1667
+ /**
1668
+ * Whether or not the stream is currently paused.
1669
+ */
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
+ */
1675
+ __publicField(this, "repeatMode");
1676
+ /**
1677
+ * Whether or not the autoplay mode is enabled. Default value: `false`
1678
+ */
1679
+ __publicField(this, "autoplay");
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
+ */
1688
+ __publicField(this, "textChannel");
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
+ */
1705
+ __publicField(this, "_taskQueue");
1706
+ /**
1707
+ * {@link DisTubeVoice} listener
1708
+ */
1709
+ __publicField(this, "_listeners");
1710
+ this.voice = voice;
1711
+ this.id = voice.id;
1712
+ this.volume = 50;
1713
+ this.songs = [];
1714
+ this.previousSongs = [];
1715
+ this.stopped = false;
1716
+ this._next = false;
1717
+ this._prev = false;
1718
+ this.playing = false;
1719
+ this.paused = false;
1720
+ this.repeatMode = 0 /* DISABLED */;
1721
+ this.autoplay = false;
1722
+ __privateSet(this, _filters, new FilterManager(this));
1723
+ this._beginTime = 0;
1724
+ this.textChannel = textChannel;
1725
+ this._taskQueue = new TaskQueue();
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
+ };
1732
+ }
1733
+ /**
1734
+ * The client user as a `GuildMember` of this queue's guild
1735
+ */
1736
+ get clientMember() {
1737
+ return this.voice.channel.guild.members.me ?? void 0;
1738
+ }
1739
+ /**
1740
+ * The filter manager of the queue
1741
+ */
1742
+ get filters() {
1743
+ return __privateGet(this, _filters);
1744
+ }
1745
+ /**
1746
+ * Formatted duration string.
1747
+ */
1748
+ get formattedDuration() {
1749
+ return formatDuration(this.duration);
1750
+ }
1751
+ /**
1752
+ * Queue's duration.
1753
+ */
1754
+ get duration() {
1755
+ return this.songs.length ? this.songs.reduce((prev, next) => prev + next.duration, 0) : 0;
1756
+ }
1757
+ /**
1758
+ * What time in the song is playing (in seconds).
1759
+ */
1760
+ get currentTime() {
1761
+ return this.voice.playbackDuration + this._beginTime;
1762
+ }
1763
+ /**
1764
+ * Formatted {@link Queue#currentTime} string.
1765
+ */
1766
+ get formattedCurrentTime() {
1767
+ return formatDuration(this.currentTime);
1768
+ }
1769
+ /**
1770
+ * The voice channel playing in.
1771
+ */
1772
+ get voiceChannel() {
1773
+ return this.clientMember?.voice?.channel ?? null;
1774
+ }
1775
+ /**
1776
+ * Get or set the stream volume. Default value: `50`.
1777
+ */
1778
+ get volume() {
1779
+ return this.voice.volume;
1780
+ }
1781
+ set volume(value) {
1782
+ this.voice.volume = value;
1783
+ }
1784
+ /**
1785
+ * @throws {DisTubeError}
1786
+ * @param song - Song to add
1787
+ * @param position - Position to add, \<= 0 to add to the end of the queue
1788
+ * @returns The guild queue
1789
+ */
1790
+ addToQueue(song, position = 0) {
1791
+ if (this.stopped) throw new DisTubeError("QUEUE_STOPPED");
1792
+ if (!song || Array.isArray(song) && !song.length) {
1793
+ throw new DisTubeError("INVALID_TYPE", ["Song", "Array<Song>"], song, "song");
1794
+ }
1795
+ if (typeof position !== "number" || !Number.isInteger(position)) {
1796
+ throw new DisTubeError("INVALID_TYPE", "integer", position, "position");
1797
+ }
1798
+ if (position <= 0) {
1799
+ if (Array.isArray(song)) this.songs.push(...song);
1800
+ else this.songs.push(song);
1801
+ } else if (Array.isArray(song)) {
1802
+ this.songs.splice(position, 0, ...song);
1803
+ } else {
1804
+ this.songs.splice(position, 0, song);
1805
+ }
1806
+ return this;
1807
+ }
1808
+ /**
1809
+ * Pause the guild stream
1810
+ * @returns The guild queue
1811
+ */
1812
+ pause() {
1813
+ if (this.paused) throw new DisTubeError("PAUSED");
1814
+ this.paused = true;
1815
+ this.voice.pause();
1816
+ return this;
1817
+ }
1818
+ /**
1819
+ * Resume the guild stream
1820
+ * @returns The guild queue
1821
+ */
1822
+ resume() {
1823
+ if (!this.paused) throw new DisTubeError("RESUMED");
1824
+ this.paused = false;
1825
+ this.voice.unpause();
1826
+ return this;
1827
+ }
1828
+ /**
1829
+ * Set the guild stream's volume
1830
+ * @param percent - The percentage of volume you want to set
1831
+ * @returns The guild queue
1832
+ */
1833
+ setVolume(percent) {
1834
+ this.volume = percent;
1835
+ return this;
1836
+ }
1837
+ /**
1838
+ * Skip the playing song if there is a next song in the queue. <info>If {@link
1839
+ * Queue#autoplay} is `true` and there is no up next song, DisTube will add and
1840
+ * play a related song.</info>
1841
+ * @returns The song will skip to
1842
+ */
1843
+ async skip() {
1844
+ await this._taskQueue.queuing();
1845
+ try {
1846
+ if (this.songs.length <= 1) {
1847
+ if (this.autoplay) await this.addRelatedSong();
1848
+ else throw new DisTubeError("NO_UP_NEXT");
1849
+ }
1850
+ const song = this.songs[1];
1851
+ this._next = true;
1852
+ this.voice.stop();
1853
+ return song;
1854
+ } finally {
1855
+ this._taskQueue.resolve();
1856
+ }
1857
+ }
1858
+ /**
1859
+ * Play the previous song if exists
1860
+ * @returns The guild queue
1861
+ */
1862
+ async previous() {
1863
+ await this._taskQueue.queuing();
1864
+ try {
1865
+ if (!this.options.savePreviousSongs) throw new DisTubeError("DISABLED_OPTION", "savePreviousSongs");
1866
+ if (this.previousSongs?.length === 0 && this.repeatMode !== 2 /* QUEUE */) {
1867
+ throw new DisTubeError("NO_PREVIOUS");
1868
+ }
1869
+ const song = this.repeatMode === 2 ? this.songs[this.songs.length - 1] : this.previousSongs[this.previousSongs.length - 1];
1870
+ this._prev = true;
1871
+ this.voice.stop();
1872
+ return song;
1873
+ } finally {
1874
+ this._taskQueue.resolve();
1875
+ }
1876
+ }
1877
+ /**
1878
+ * Shuffle the queue's songs
1879
+ * @returns The guild queue
1880
+ */
1881
+ async shuffle() {
1882
+ await this._taskQueue.queuing();
1883
+ try {
1884
+ const playing = this.songs.shift();
1885
+ if (playing === void 0) return this;
1886
+ for (let i = this.songs.length - 1; i > 0; i--) {
1887
+ const j = Math.floor(Math.random() * (i + 1));
1888
+ [this.songs[i], this.songs[j]] = [this.songs[j], this.songs[i]];
1889
+ }
1890
+ this.songs.unshift(playing);
1891
+ return this;
1892
+ } finally {
1893
+ this._taskQueue.resolve();
1894
+ }
1895
+ }
1896
+ /**
1897
+ * Jump to the song position in the queue. The next one is 1, 2,... The previous
1898
+ * one is -1, -2,...
1899
+ * if `num` is invalid number
1900
+ * @param position - The song position to play
1901
+ * @returns The new Song will be played
1902
+ */
1903
+ async jump(position) {
1904
+ await this._taskQueue.queuing();
1905
+ try {
1906
+ if (typeof position !== "number") throw new DisTubeError("INVALID_TYPE", "number", position, "position");
1907
+ if (!position || position > this.songs.length || -position > this.previousSongs.length) {
1908
+ throw new DisTubeError("NO_SONG_POSITION");
1909
+ }
1910
+ let nextSong;
1911
+ if (position > 0) {
1912
+ const nextSongs = this.songs.splice(position - 1);
1913
+ if (this.options.savePreviousSongs) {
1914
+ this.previousSongs.push(...this.songs);
1915
+ } else {
1916
+ this.previousSongs.push(...this.songs.map((s) => ({ id: s.id })));
1917
+ }
1918
+ this.songs = nextSongs;
1919
+ this._next = true;
1920
+ nextSong = nextSongs[1];
1921
+ } else if (!this.options.savePreviousSongs) {
1922
+ throw new DisTubeError("DISABLED_OPTION", "savePreviousSongs");
1923
+ } else {
1924
+ this._prev = true;
1925
+ if (position !== -1) this.songs.unshift(...this.previousSongs.splice(position + 1));
1926
+ nextSong = this.previousSongs[this.previousSongs.length - 1];
1927
+ }
1928
+ this.voice.stop();
1929
+ return nextSong;
1930
+ } finally {
1931
+ this._taskQueue.resolve();
1932
+ }
1933
+ }
1934
+ /**
1935
+ * Set the repeat mode of the guild queue.
1936
+ * Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
1937
+ * @param mode - The repeat modes (toggle if `undefined`)
1938
+ * @returns The new repeat mode
1939
+ */
1940
+ setRepeatMode(mode) {
1941
+ if (mode !== void 0 && !Object.values(RepeatMode).includes(mode)) {
1942
+ throw new DisTubeError("INVALID_TYPE", ["RepeatMode", "undefined"], mode, "mode");
1943
+ }
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;
1947
+ return this.repeatMode;
1948
+ }
1949
+ /**
1950
+ * Set the playing time to another position
1951
+ * @param time - Time in seconds
1952
+ * @returns The guild queue
1953
+ */
1954
+ seek(time) {
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);
1959
+ return this;
1960
+ }
1961
+ /**
1962
+ * Add a related song of the playing song to the queue
1963
+ * @returns The added song
1964
+ */
1965
+ async addRelatedSong() {
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;
1980
+ this.addToQueue(song);
1981
+ return song;
1982
+ }
1983
+ /**
1984
+ * Stop the guild stream and delete the queue
1985
+ */
1986
+ async stop() {
1987
+ await this._taskQueue.queuing();
1988
+ try {
1989
+ this.playing = false;
1990
+ this.paused = false;
1991
+ this.stopped = true;
1992
+ this.voice.stop();
1993
+ this.remove();
1994
+ } finally {
1995
+ this._taskQueue.resolve();
1996
+ }
1997
+ }
1998
+ /**
1999
+ * Remove the queue from the manager
2000
+ */
2001
+ remove() {
2002
+ this.stopped = true;
2003
+ this.songs = [];
2004
+ this.previousSongs = [];
2005
+ if (this._listeners) {
2006
+ for (const event of objectKeys(this._listeners)) {
2007
+ this.voice.off(event, this._listeners[event]);
2008
+ }
2009
+ }
2010
+ this.queues.remove(this.id);
2011
+ this.emit("deleteQueue" /* DELETE_QUEUE */, this);
2012
+ }
2013
+ /**
2014
+ * Toggle autoplay mode
2015
+ * @returns Autoplay mode state
2016
+ */
2017
+ toggleAutoplay() {
2018
+ this.autoplay = !this.autoplay;
2019
+ return this.autoplay;
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
+ }
2030
+ };
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");
2038
+ __name(_Queue, "Queue");
2039
+ var Queue = _Queue;
2040
+
2041
+ // src/struct/Plugin.ts
2042
+ var _Plugin = class _Plugin {
2043
+ constructor() {
2044
+ /**
2045
+ * DisTube
2046
+ */
2047
+ __publicField(this, "distube");
2048
+ }
2049
+ init(distube) {
2050
+ this.distube = distube;
2051
+ }
2052
+ };
2053
+ __name(_Plugin, "Plugin");
2054
+ var Plugin = _Plugin;
2055
+
2056
+ // src/struct/ExtractorPlugin.ts
2057
+ var _ExtractorPlugin = class _ExtractorPlugin extends Plugin {
2058
+ constructor() {
2059
+ super(...arguments);
2060
+ __publicField(this, "type", "extractor" /* EXTRACTOR */);
2061
+ }
2062
+ };
2063
+ __name(_ExtractorPlugin, "ExtractorPlugin");
2064
+ var ExtractorPlugin = _ExtractorPlugin;
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
+
2086
+ // src/util.ts
2087
+ var import_url = require("url");
2088
+ var import_discord3 = require("discord.js");
2089
+ var formatInt = /* @__PURE__ */ __name((int) => int < 10 ? `0${int}` : int, "formatInt");
2090
+ function formatDuration(sec) {
2091
+ if (!sec || !Number(sec)) return "00:00";
2092
+ const seconds = Math.floor(sec % 60);
2093
+ const minutes = Math.floor(sec % 3600 / 60);
2094
+ const hours = Math.floor(sec / 3600);
2095
+ if (hours > 0) return `${formatInt(hours)}:${formatInt(minutes)}:${formatInt(seconds)}`;
2096
+ if (minutes > 0) return `${formatInt(minutes)}:${formatInt(seconds)}`;
2097
+ return `00:${formatInt(seconds)}`;
2098
+ }
2099
+ __name(formatDuration, "formatDuration");
2100
+ var SUPPORTED_PROTOCOL = ["https:", "http:", "file:"];
2101
+ function isURL(input) {
2102
+ if (typeof input !== "string" || input.includes(" ")) return false;
2103
+ try {
2104
+ const url = new import_url.URL(input);
2105
+ if (!SUPPORTED_PROTOCOL.some((p) => p === url.protocol)) return false;
2106
+ } catch {
2107
+ return false;
2108
+ }
2109
+ return true;
2110
+ }
2111
+ __name(isURL, "isURL");
2112
+ function checkIntents(options) {
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");
2115
+ }
2116
+ __name(checkIntents, "checkIntents");
2117
+ function isVoiceChannelEmpty(voiceState) {
2118
+ const guild = voiceState.guild;
2119
+ const clientId = voiceState.client.user?.id;
2120
+ if (!guild || !clientId) return false;
2121
+ const voiceChannel = guild.members.me?.voice?.channel;
2122
+ if (!voiceChannel) return false;
2123
+ const members = voiceChannel.members.filter((m) => !m.user.bot);
2124
+ return !members.size;
2125
+ }
2126
+ __name(isVoiceChannelEmpty, "isVoiceChannelEmpty");
2127
+ function isSnowflake(id) {
2128
+ try {
2129
+ return import_discord3.SnowflakeUtil.deconstruct(id).timestamp > import_discord3.SnowflakeUtil.epoch;
2130
+ } catch {
2131
+ return false;
2132
+ }
2133
+ }
2134
+ __name(isSnowflake, "isSnowflake");
2135
+ function isMemberInstance(member) {
2136
+ return Boolean(member) && isSnowflake(member.id) && isSnowflake(member.guild?.id) && isSnowflake(member.user?.id) && member.id === member.user.id;
2137
+ }
2138
+ __name(isMemberInstance, "isMemberInstance");
2139
+ function isTextChannelInstance(channel) {
2140
+ return Boolean(channel) && isSnowflake(channel.id) && isSnowflake(channel.guildId || channel.guild?.id) && import_discord3.Constants.TextBasedChannelTypes.includes(channel.type) && typeof channel.send === "function" && (typeof channel.nsfw === "boolean" || typeof channel.parent?.nsfw === "boolean");
2141
+ }
2142
+ __name(isTextChannelInstance, "isTextChannelInstance");
2143
+ function isMessageInstance(message) {
2144
+ return Boolean(message) && isSnowflake(message.id) && isSnowflake(message.guildId || message.guild?.id) && isMemberInstance(message.member) && isTextChannelInstance(message.channel) && import_discord3.Constants.NonSystemMessageTypes.includes(message.type) && message.member.id === message.author?.id;
2145
+ }
2146
+ __name(isMessageInstance, "isMessageInstance");
2147
+ function isSupportedVoiceChannel(channel) {
2148
+ return Boolean(channel) && isSnowflake(channel.id) && isSnowflake(channel.guildId || channel.guild?.id) && import_discord3.Constants.VoiceBasedChannelTypes.includes(channel.type);
2149
+ }
2150
+ __name(isSupportedVoiceChannel, "isSupportedVoiceChannel");
2151
+ function isGuildInstance(guild) {
2152
+ return Boolean(guild) && isSnowflake(guild.id) && isSnowflake(guild.ownerId) && typeof guild.name === "string";
2153
+ }
2154
+ __name(isGuildInstance, "isGuildInstance");
2155
+ function resolveGuildId(resolvable) {
2156
+ let guildId;
2157
+ if (typeof resolvable === "string") {
2158
+ guildId = resolvable;
2159
+ } else if (isObject(resolvable)) {
2160
+ if ("guildId" in resolvable && resolvable.guildId) {
2161
+ guildId = resolvable.guildId;
2162
+ } else if (resolvable instanceof Queue || resolvable instanceof DisTubeVoice || isGuildInstance(resolvable)) {
2163
+ guildId = resolvable.id;
2164
+ } else if ("guild" in resolvable && isGuildInstance(resolvable.guild)) {
2165
+ guildId = resolvable.guild.id;
2166
+ }
2167
+ }
2168
+ if (!isSnowflake(guildId)) throw new DisTubeError("INVALID_TYPE", "GuildIdResolvable", resolvable);
2169
+ return guildId;
2170
+ }
2171
+ __name(resolveGuildId, "resolveGuildId");
2172
+ function isClientInstance(client) {
2173
+ return Boolean(client) && typeof client.login === "function";
2174
+ }
2175
+ __name(isClientInstance, "isClientInstance");
2176
+ function checkInvalidKey(target, source, sourceName) {
2177
+ if (!isObject(target)) throw new DisTubeError("INVALID_TYPE", "object", target, sourceName);
2178
+ const sourceKeys = Array.isArray(source) ? source : objectKeys(source);
2179
+ const invalidKey = objectKeys(target).find((key) => !sourceKeys.includes(key));
2180
+ if (invalidKey) throw new DisTubeError("INVALID_KEY", sourceName, invalidKey);
2181
+ }
2182
+ __name(checkInvalidKey, "checkInvalidKey");
2183
+ function isObject(obj) {
2184
+ return typeof obj === "object" && obj !== null && !Array.isArray(obj);
2185
+ }
2186
+ __name(isObject, "isObject");
2187
+ function objectKeys(obj) {
2188
+ if (!isObject(obj)) return [];
2189
+ return Object.keys(obj);
2190
+ }
2191
+ __name(objectKeys, "objectKeys");
2192
+ function isNsfwChannel(channel) {
2193
+ if (!isTextChannelInstance(channel)) return false;
2194
+ if (channel.isThread()) return channel.parent?.nsfw ?? false;
2195
+ return channel.nsfw;
2196
+ }
2197
+ __name(isNsfwChannel, "isNsfwChannel");
2198
+ var isTruthy = /* @__PURE__ */ __name((x) => Boolean(x), "isTruthy");
2199
+
2200
+ // src/DisTube.ts
2201
+ var import_tiny_typed_emitter3 = require("tiny-typed-emitter");
2202
+ var { version } = require_package();
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
+ */
2211
+ constructor(client, opts = {}) {
2212
+ super();
2213
+ __privateAdd(this, _DisTube_instances);
2214
+ /**
2215
+ * DisTube internal handler
2216
+ */
2217
+ __publicField(this, "handler");
2218
+ /**
2219
+ * DisTube options
2220
+ */
2221
+ __publicField(this, "options");
2222
+ /**
2223
+ * Discord.js v14 client
2224
+ */
2225
+ __publicField(this, "client");
2226
+ /**
2227
+ * Queues manager
2228
+ */
2229
+ __publicField(this, "queues");
2230
+ /**
2231
+ * DisTube voice connections manager
2232
+ */
2233
+ __publicField(this, "voices");
2234
+ /**
2235
+ * DisTube plugins
2236
+ */
2237
+ __publicField(this, "plugins");
2238
+ /**
2239
+ * DisTube ffmpeg audio filters
2240
+ */
2241
+ __publicField(this, "filters");
2242
+ this.setMaxListeners(1);
2243
+ if (!isClientInstance(client)) throw new DisTubeError("INVALID_TYPE", "Discord.Client", client, "client");
2244
+ this.client = client;
2245
+ checkIntents(client.options);
2246
+ this.options = new Options(opts);
2247
+ this.voices = new DisTubeVoiceManager(this);
2248
+ this.handler = new DisTubeHandler(this);
2249
+ this.queues = new QueueManager(this);
2250
+ this.filters = { ...defaultFilters, ...this.options.customFilters };
2251
+ this.plugins = [...this.options.plugins];
2252
+ this.plugins.forEach((p) => p.init(this));
2253
+ }
2254
+ static get version() {
2255
+ return version;
2256
+ }
2257
+ /**
2258
+ * DisTube version
2259
+ */
2260
+ get version() {
2261
+ return version;
2262
+ }
2263
+ /**
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.
2266
+ * @throws {@link DisTubeError}
2267
+ * @param voiceChannel - The channel will be joined if the bot isn't in any channels, the bot will be
2268
+ * moved to this channel if {@link DisTubeOptions}.joinNewVoiceChannel is `true`
2269
+ * @param song - URL | Search string | {@link Song} | {@link Playlist}
2270
+ * @param options - Optional options
2271
+ */
2272
+ async play(voiceChannel, song, options = {}) {
2273
+ if (!isSupportedVoiceChannel(voiceChannel)) {
2274
+ throw new DisTubeError("INVALID_TYPE", "BaseGuildVoiceChannel", voiceChannel, "voiceChannel");
2275
+ }
2276
+ if (!isObject(options)) throw new DisTubeError("INVALID_TYPE", "object", options, "options");
2277
+ const { textChannel, member, skip, message, metadata } = {
2278
+ member: voiceChannel.guild.members.me ?? void 0,
2279
+ textChannel: options?.message?.channel,
2280
+ skip: false,
2281
+ ...options
2282
+ };
2283
+ const position = Number(options.position) || (skip ? 1 : 0);
2284
+ if (message && !isMessageInstance(message)) {
2285
+ throw new DisTubeError("INVALID_TYPE", ["Discord.Message", "a falsy value"], message, "options.message");
2286
+ }
2287
+ if (textChannel && !isTextChannelInstance(textChannel)) {
2288
+ throw new DisTubeError("INVALID_TYPE", "Discord.GuildTextBasedChannel", textChannel, "options.textChannel");
2289
+ }
2290
+ if (member && !isMemberInstance(member)) {
2291
+ throw new DisTubeError("INVALID_TYPE", "Discord.GuildMember", member, "options.member");
2292
+ }
2293
+ const queue = this.getQueue(voiceChannel) || await this.queues.create(voiceChannel, textChannel);
2294
+ await queue._taskQueue.queuing();
2295
+ try {
2296
+ this.debug(`[${queue.id}] Playing input: ${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");
2303
+ }
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);
2308
+ } else {
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);
2315
+ }
2316
+ if (!queue.playing) await queue.play();
2317
+ } catch (e) {
2318
+ if (!(e instanceof DisTubeError)) {
2319
+ this.debug(`[${queue.id}] Unexpected error while playing song: ${e.stack || e.message}`);
2320
+ try {
2321
+ e.name = "PlayError";
2322
+ e.message = `${typeof song === "string" ? song : song.url}
2323
+ ${e.message}`;
2324
+ } catch {
2325
+ }
2326
+ }
2327
+ throw e;
2328
+ } finally {
2329
+ queue._taskQueue.resolve();
2330
+ }
2331
+ }
2332
+ /**
2333
+ * Create a custom playlist
2334
+ * @param songs - Array of url or Song
2335
+ * @param options - Optional options
2336
+ */
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");
2342
+ if (member && !isMemberInstance(member)) {
2343
+ throw new DisTubeError("INVALID_TYPE", "Discord.Member", member, "options.member");
2344
+ }
2345
+ let resolvedSongs;
2346
+ if (parallel !== false) {
2347
+ const promises = filteredSongs.map(
2348
+ (song) => this.handler.resolve(song, { member, metadata }).catch(() => void 0)
2349
+ );
2350
+ resolvedSongs = (await Promise.all(promises)).filter((s) => s instanceof Song);
2351
+ } else {
2352
+ resolvedSongs = [];
2353
+ for (const song of filteredSongs) {
2354
+ const resolved = await this.handler.resolve(song, { member, metadata }).catch(() => void 0);
2355
+ if (resolved instanceof Song) resolvedSongs.push(resolved);
2356
+ }
2357
+ }
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
+ );
2368
+ }
2369
+ /**
2370
+ * Get the guild queue
2371
+ * @param guild - The type can be resolved to give a {@link Queue}
2372
+ */
2373
+ getQueue(guild) {
2374
+ return this.queues.get(guild);
2375
+ }
2376
+ /**
2377
+ * Pause the guild stream
2378
+ * @param guild - The type can be resolved to give a {@link Queue}
2379
+ * @returns The guild queue
2380
+ */
2381
+ pause(guild) {
2382
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).pause();
2383
+ }
2384
+ /**
2385
+ * Resume the guild stream
2386
+ * @param guild - The type can be resolved to give a {@link Queue}
2387
+ * @returns The guild queue
2388
+ */
2389
+ resume(guild) {
2390
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).resume();
2391
+ }
2392
+ /**
2393
+ * Stop the guild stream
2394
+ * @param guild - The type can be resolved to give a {@link Queue}
2395
+ */
2396
+ stop(guild) {
2397
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).stop();
2398
+ }
2399
+ /**
2400
+ * Set the guild stream's volume
2401
+ * @param guild - The type can be resolved to give a {@link Queue}
2402
+ * @param percent - The percentage of volume you want to set
2403
+ * @returns The guild queue
2404
+ */
2405
+ setVolume(guild, percent) {
2406
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).setVolume(percent);
2407
+ }
2408
+ /**
2409
+ * Skip the playing song if there is a next song in the queue. <info>If {@link
2410
+ * Queue#autoplay} is `true` and there is no up next song, DisTube will add and
2411
+ * play a related song.</info>
2412
+ * @param guild - The type can be resolved to give a {@link Queue}
2413
+ * @returns The new Song will be played
2414
+ */
2415
+ skip(guild) {
2416
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).skip();
2417
+ }
2418
+ /**
2419
+ * Play the previous song
2420
+ * @param guild - The type can be resolved to give a {@link Queue}
2421
+ * @returns The new Song will be played
2422
+ */
2423
+ previous(guild) {
2424
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).previous();
2425
+ }
2426
+ /**
2427
+ * Shuffle the guild queue songs
2428
+ * @param guild - The type can be resolved to give a {@link Queue}
2429
+ * @returns The guild queue
2430
+ */
2431
+ shuffle(guild) {
2432
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).shuffle();
2433
+ }
2434
+ /**
2435
+ * Jump to the song number in the queue. The next one is 1, 2,... The previous one
2436
+ * is -1, -2,...
2437
+ * @param guild - The type can be resolved to give a {@link Queue}
2438
+ * @param num - The song number to play
2439
+ * @returns The new Song will be played
2440
+ */
2441
+ jump(guild, num) {
2442
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).jump(num);
2443
+ }
2444
+ /**
2445
+ * Set the repeat mode of the guild queue.
2446
+ * Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
2447
+ * @param guild - The type can be resolved to give a {@link Queue}
2448
+ * @param mode - The repeat modes (toggle if `undefined`)
2449
+ * @returns The new repeat mode
2450
+ */
2451
+ setRepeatMode(guild, mode) {
2452
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).setRepeatMode(mode);
2453
+ }
2454
+ /**
2455
+ * Toggle autoplay mode
2456
+ * @param guild - The type can be resolved to give a {@link Queue}
2457
+ * @returns Autoplay mode state
2458
+ */
2459
+ toggleAutoplay(guild) {
2460
+ const queue = __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild);
2461
+ queue.autoplay = !queue.autoplay;
2462
+ return queue.autoplay;
2463
+ }
2464
+ /**
2465
+ * Add related song to the queue
2466
+ * @param guild - The type can be resolved to give a {@link Queue}
2467
+ * @returns The guild queue
2468
+ */
2469
+ addRelatedSong(guild) {
2470
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).addRelatedSong();
2471
+ }
2472
+ /**
2473
+ * Set the playing time to another position
2474
+ * @param guild - The type can be resolved to give a {@link Queue}
2475
+ * @param time - Time in seconds
2476
+ * @returns Seeked queue
2477
+ */
2478
+ seek(guild, time) {
2479
+ return __privateMethod(this, _DisTube_instances, getQueue_fn).call(this, guild).seek(time);
2480
+ }
2481
+ /**
2482
+ * Emit error event
2483
+ * @param error - error
2484
+ * @param queue - The queue encountered the error
2485
+ * @param song - The playing song when encountered the error
2486
+ */
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);
2496
+ }
2497
+ };
2498
+ _DisTube_instances = new WeakSet();
2499
+ getQueue_fn = /* @__PURE__ */ __name(function(guild) {
2500
+ const queue = this.getQueue(guild);
2501
+ if (!queue) throw new DisTubeError("NO_QUEUE");
2502
+ return queue;
2503
+ }, "#getQueue");
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);
2588
+ var DisTube = _DisTube;
2589
+ // Annotate the CommonJS export names for ESM import in node:
2590
+ 0 && (module.exports = {
2591
+ BaseManager,
2592
+ DisTube,
2593
+ DisTubeBase,
2594
+ DisTubeError,
2595
+ DisTubeHandler,
2596
+ DisTubeStream,
2597
+ DisTubeVoice,
2598
+ DisTubeVoiceManager,
2599
+ Events,
2600
+ ExtractorPlugin,
2601
+ FilterManager,
2602
+ GuildIdManager,
2603
+ InfoExtractorPlugin,
2604
+ Options,
2605
+ PlayableExtractorPlugin,
2606
+ Playlist,
2607
+ Plugin,
2608
+ PluginType,
2609
+ Queue,
2610
+ QueueManager,
2611
+ RepeatMode,
2612
+ Song,
2613
+ TaskQueue,
2614
+ checkFFmpeg,
2615
+ checkIntents,
2616
+ checkInvalidKey,
2617
+ defaultFilters,
2618
+ defaultOptions,
2619
+ formatDuration,
2620
+ isClientInstance,
2621
+ isGuildInstance,
2622
+ isMemberInstance,
2623
+ isMessageInstance,
2624
+ isNsfwChannel,
2625
+ isObject,
2626
+ isSnowflake,
2627
+ isSupportedVoiceChannel,
2628
+ isTextChannelInstance,
2629
+ isTruthy,
2630
+ isURL,
2631
+ isVoiceChannelEmpty,
2632
+ objectKeys,
2633
+ resolveGuildId,
2634
+ version
2635
+ });
2636
+ //# sourceMappingURL=index.js.map