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