distube 4.0.4 → 4.0.6
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 +3 -8
- package/dist/index.d.ts +39 -35
- package/dist/index.js +1103 -246
- package/dist/index.js.map +1 -1
- package/package.json +33 -31
package/dist/index.js
CHANGED
|
@@ -23,6 +23,10 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
23
23
|
return to;
|
|
24
24
|
};
|
|
25
25
|
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
26
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
27
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
28
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
29
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
30
|
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
31
|
mod
|
|
28
32
|
));
|
|
@@ -59,7 +63,7 @@ var require_package = __commonJS({
|
|
|
59
63
|
"package.json"(exports, module2) {
|
|
60
64
|
module2.exports = {
|
|
61
65
|
name: "distube",
|
|
62
|
-
version: "4.0.
|
|
66
|
+
version: "4.0.6",
|
|
63
67
|
description: "A Discord.js module to simplify your music commands and play songs with audio filters on Discord without any API key.",
|
|
64
68
|
main: "./dist/index.js",
|
|
65
69
|
types: "./dist/index.d.ts",
|
|
@@ -83,7 +87,8 @@ var require_package = __commonJS({
|
|
|
83
87
|
postinstall: "husky install",
|
|
84
88
|
prepublishOnly: "yarn lint && yarn test",
|
|
85
89
|
prepack: "yarn build && pinst --disable",
|
|
86
|
-
postpack: "pinst --enable"
|
|
90
|
+
postpack: "pinst --enable",
|
|
91
|
+
"dev:add-docs-to-worktree": "git worktree add --track -b docs docs origin/docs"
|
|
87
92
|
},
|
|
88
93
|
repository: {
|
|
89
94
|
type: "git",
|
|
@@ -119,44 +124,45 @@ var require_package = __commonJS({
|
|
|
119
124
|
],
|
|
120
125
|
homepage: "https://distube.js.org/",
|
|
121
126
|
dependencies: {
|
|
122
|
-
"@distube/ytdl-core": "^4.11.
|
|
127
|
+
"@distube/ytdl-core": "^4.11.17",
|
|
123
128
|
"@distube/ytpl": "^1.1.1",
|
|
124
|
-
"@distube/ytsr": "^1.1.
|
|
125
|
-
"prism-media": "
|
|
129
|
+
"@distube/ytsr": "^1.1.9",
|
|
130
|
+
"prism-media": "npm:@distube/prism-media@latest",
|
|
126
131
|
"tiny-typed-emitter": "^2.1.0",
|
|
127
|
-
tslib: "^2.
|
|
128
|
-
undici: "^5.
|
|
132
|
+
tslib: "^2.6.1",
|
|
133
|
+
undici: "^5.22.1"
|
|
129
134
|
},
|
|
130
135
|
devDependencies: {
|
|
131
|
-
"@babel/core": "^7.
|
|
136
|
+
"@babel/core": "^7.22.9",
|
|
132
137
|
"@babel/plugin-proposal-class-properties": "^7.18.6",
|
|
133
|
-
"@babel/plugin-proposal-object-rest-spread": "^7.
|
|
134
|
-
"@babel/
|
|
135
|
-
"@babel/preset-
|
|
136
|
-
"@
|
|
137
|
-
"@commitlint/
|
|
138
|
-
"@
|
|
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",
|
|
139
145
|
"@distubejs/docgen": "distubejs/docgen",
|
|
140
|
-
"@types/jest": "^
|
|
141
|
-
"@types/node": "^
|
|
142
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
143
|
-
"@typescript-eslint/parser": "^
|
|
144
|
-
"babel-jest": "^
|
|
145
|
-
"discord.js": "^14.0
|
|
146
|
-
eslint: "^8.
|
|
146
|
+
"@types/jest": "^29.5.3",
|
|
147
|
+
"@types/node": "^20.4.5",
|
|
148
|
+
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
|
149
|
+
"@typescript-eslint/parser": "^6.2.0",
|
|
150
|
+
"babel-jest": "^29.6.2",
|
|
151
|
+
"discord.js": "^14.11.0",
|
|
152
|
+
eslint: "^8.45.0",
|
|
147
153
|
"eslint-config-distube": "^1.6.4",
|
|
148
|
-
"eslint-config-prettier": "^8.
|
|
149
|
-
"eslint-plugin-deprecation": "^1.
|
|
150
|
-
"eslint-plugin-jsdoc": "^
|
|
151
|
-
husky: "^8.0.
|
|
152
|
-
jest: "^
|
|
154
|
+
"eslint-config-prettier": "^8.9.0",
|
|
155
|
+
"eslint-plugin-deprecation": "^1.5.0",
|
|
156
|
+
"eslint-plugin-jsdoc": "^46.4.5",
|
|
157
|
+
husky: "^8.0.3",
|
|
158
|
+
jest: "^29.6.2",
|
|
153
159
|
"jsdoc-babel": "^0.5.0",
|
|
154
160
|
"nano-staged": "^0.8.0",
|
|
155
|
-
"npm-check-updates": "^16.
|
|
161
|
+
"npm-check-updates": "^16.10.17",
|
|
156
162
|
pinst: "^3.0.0",
|
|
157
|
-
prettier: "^
|
|
158
|
-
tsup: "^
|
|
159
|
-
typescript: "^
|
|
163
|
+
prettier: "^3.0.0",
|
|
164
|
+
tsup: "^7.1.0",
|
|
165
|
+
typescript: "^5.1.6"
|
|
160
166
|
},
|
|
161
167
|
peerDependencies: {
|
|
162
168
|
"@discordjs/opus": "*",
|
|
@@ -180,7 +186,7 @@ var require_package = __commonJS({
|
|
|
180
186
|
engines: {
|
|
181
187
|
node: ">=16.9.0"
|
|
182
188
|
},
|
|
183
|
-
packageManager: "yarn@3.
|
|
189
|
+
packageManager: "yarn@3.6.1"
|
|
184
190
|
};
|
|
185
191
|
}
|
|
186
192
|
});
|
|
@@ -226,6 +232,7 @@ __export(src_exports, {
|
|
|
226
232
|
isGuildInstance: () => isGuildInstance,
|
|
227
233
|
isMemberInstance: () => isMemberInstance,
|
|
228
234
|
isMessageInstance: () => isMessageInstance,
|
|
235
|
+
isNsfwChannel: () => isNsfwChannel,
|
|
229
236
|
isObject: () => isObject,
|
|
230
237
|
isRecord: () => isRecord,
|
|
231
238
|
isSnowflake: () => isSnowflake,
|
|
@@ -364,13 +371,13 @@ var ERROR_MESSAGES = {
|
|
|
364
371
|
var haveCode = /* @__PURE__ */ __name((code) => Object.keys(ERROR_MESSAGES).includes(code), "haveCode");
|
|
365
372
|
var parseMessage = /* @__PURE__ */ __name((m, ...args) => typeof m === "string" ? m : m(...args), "parseMessage");
|
|
366
373
|
var getErrorMessage = /* @__PURE__ */ __name((code, ...args) => haveCode(code) ? parseMessage(ERROR_MESSAGES[code], ...args) : args[0], "getErrorMessage");
|
|
367
|
-
var
|
|
374
|
+
var _DisTubeError = class _DisTubeError extends Error {
|
|
368
375
|
constructor(code, ...args) {
|
|
369
376
|
super(getErrorMessage(code, ...args));
|
|
370
377
|
__publicField(this, "errorCode");
|
|
371
378
|
this.errorCode = code;
|
|
372
379
|
if (Error.captureStackTrace)
|
|
373
|
-
Error.captureStackTrace(this,
|
|
380
|
+
Error.captureStackTrace(this, _DisTubeError);
|
|
374
381
|
}
|
|
375
382
|
get name() {
|
|
376
383
|
return `DisTubeError [${this.errorCode}]`;
|
|
@@ -379,10 +386,11 @@ var DisTubeError = class extends Error {
|
|
|
379
386
|
return this.errorCode;
|
|
380
387
|
}
|
|
381
388
|
};
|
|
382
|
-
__name(
|
|
389
|
+
__name(_DisTubeError, "DisTubeError");
|
|
390
|
+
var DisTubeError = _DisTubeError;
|
|
383
391
|
|
|
384
392
|
// src/struct/TaskQueue.ts
|
|
385
|
-
var
|
|
393
|
+
var _Task = class _Task {
|
|
386
394
|
constructor(resolveInfo) {
|
|
387
395
|
__publicField(this, "resolve");
|
|
388
396
|
__publicField(this, "promise");
|
|
@@ -393,33 +401,64 @@ var Task = class {
|
|
|
393
401
|
});
|
|
394
402
|
}
|
|
395
403
|
};
|
|
396
|
-
__name(
|
|
404
|
+
__name(_Task, "Task");
|
|
405
|
+
var Task = _Task;
|
|
397
406
|
var _tasks;
|
|
398
|
-
var
|
|
407
|
+
var _TaskQueue = class _TaskQueue {
|
|
399
408
|
constructor() {
|
|
409
|
+
/**
|
|
410
|
+
* The task array
|
|
411
|
+
* @type {Task[]}
|
|
412
|
+
* @private
|
|
413
|
+
*/
|
|
400
414
|
__privateAdd(this, _tasks, []);
|
|
401
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
|
+
*/
|
|
402
421
|
queuing(resolveInfo = false) {
|
|
403
422
|
const next = this.remaining ? __privateGet(this, _tasks)[__privateGet(this, _tasks).length - 1].promise : Promise.resolve();
|
|
404
423
|
__privateGet(this, _tasks).push(new Task(resolveInfo));
|
|
405
424
|
return next;
|
|
406
425
|
}
|
|
426
|
+
/**
|
|
427
|
+
* Removes the finished task and processes the next task
|
|
428
|
+
*/
|
|
407
429
|
resolve() {
|
|
408
430
|
__privateGet(this, _tasks).shift()?.resolve();
|
|
409
431
|
}
|
|
432
|
+
/**
|
|
433
|
+
* The remaining number of tasks
|
|
434
|
+
* @type {number}
|
|
435
|
+
*/
|
|
410
436
|
get remaining() {
|
|
411
437
|
return __privateGet(this, _tasks).length;
|
|
412
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* Whether or not having a resolving info task
|
|
441
|
+
* @type {boolean}
|
|
442
|
+
*/
|
|
413
443
|
get hasResolveTask() {
|
|
414
444
|
return !!__privateGet(this, _tasks).find((t) => t.resolveInfo);
|
|
415
445
|
}
|
|
416
446
|
};
|
|
417
|
-
__name(TaskQueue, "TaskQueue");
|
|
418
447
|
_tasks = new WeakMap();
|
|
448
|
+
__name(_TaskQueue, "TaskQueue");
|
|
449
|
+
var TaskQueue = _TaskQueue;
|
|
419
450
|
|
|
420
451
|
// src/struct/Playlist.ts
|
|
421
452
|
var _metadata, _member;
|
|
422
|
-
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
|
+
*/
|
|
423
462
|
constructor(playlist, options = {}) {
|
|
424
463
|
__publicField(this, "source");
|
|
425
464
|
__publicField(this, "songs");
|
|
@@ -448,7 +487,8 @@ var Playlist = class {
|
|
|
448
487
|
if (!Array.isArray(playlist.songs) || !playlist.songs.length)
|
|
449
488
|
throw new DisTubeError("EMPTY_PLAYLIST");
|
|
450
489
|
this.songs = playlist.songs;
|
|
451
|
-
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`);
|
|
452
492
|
this.url = playlist.url || playlist.webpage_url;
|
|
453
493
|
this.thumbnail = playlist.thumbnail || this.songs[0].thumbnail;
|
|
454
494
|
this.member = member || playlist.member || void 0;
|
|
@@ -459,12 +499,24 @@ var Playlist = class {
|
|
|
459
499
|
this[key] = value;
|
|
460
500
|
this.metadata = metadata;
|
|
461
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* Playlist duration in second.
|
|
504
|
+
* @type {number}
|
|
505
|
+
*/
|
|
462
506
|
get duration() {
|
|
463
507
|
return this.songs?.reduce((prev, next) => prev + (next.duration || 0), 0) || 0;
|
|
464
508
|
}
|
|
509
|
+
/**
|
|
510
|
+
* Formatted duration string `hh:mm:ss`.
|
|
511
|
+
* @type {string}
|
|
512
|
+
*/
|
|
465
513
|
get formattedDuration() {
|
|
466
514
|
return formatDuration(this.duration);
|
|
467
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* User requested.
|
|
518
|
+
* @type {Discord.GuildMember?}
|
|
519
|
+
*/
|
|
468
520
|
get member() {
|
|
469
521
|
return __privateGet(this, _member);
|
|
470
522
|
}
|
|
@@ -474,6 +526,10 @@ var Playlist = class {
|
|
|
474
526
|
__privateSet(this, _member, member);
|
|
475
527
|
this.songs.map((s) => s.constructor.name === "Song" && (s.member = this.member));
|
|
476
528
|
}
|
|
529
|
+
/**
|
|
530
|
+
* User requested.
|
|
531
|
+
* @type {Discord.User?}
|
|
532
|
+
*/
|
|
477
533
|
get user() {
|
|
478
534
|
return this.member?.user;
|
|
479
535
|
}
|
|
@@ -485,12 +541,17 @@ var Playlist = class {
|
|
|
485
541
|
this.songs.map((s) => s.constructor.name === "Song" && (s.metadata = metadata));
|
|
486
542
|
}
|
|
487
543
|
};
|
|
488
|
-
__name(Playlist, "Playlist");
|
|
489
544
|
_metadata = new WeakMap();
|
|
490
545
|
_member = new WeakMap();
|
|
546
|
+
__name(_Playlist, "Playlist");
|
|
547
|
+
var Playlist = _Playlist;
|
|
491
548
|
|
|
492
549
|
// src/struct/SearchResult.ts
|
|
493
|
-
var
|
|
550
|
+
var _ISearchResult = class _ISearchResult {
|
|
551
|
+
/**
|
|
552
|
+
* Create a search result
|
|
553
|
+
* @param {Object} info ytsr result
|
|
554
|
+
*/
|
|
494
555
|
constructor(info) {
|
|
495
556
|
__publicField(this, "source");
|
|
496
557
|
__publicField(this, "id");
|
|
@@ -507,8 +568,9 @@ var ISearchResult = class {
|
|
|
507
568
|
};
|
|
508
569
|
}
|
|
509
570
|
};
|
|
510
|
-
__name(
|
|
511
|
-
var
|
|
571
|
+
__name(_ISearchResult, "ISearchResult");
|
|
572
|
+
var ISearchResult = _ISearchResult;
|
|
573
|
+
var _SearchResultVideo = class _SearchResultVideo extends ISearchResult {
|
|
512
574
|
constructor(info) {
|
|
513
575
|
super(info);
|
|
514
576
|
__publicField(this, "type");
|
|
@@ -531,8 +593,9 @@ var SearchResultVideo = class extends ISearchResult {
|
|
|
531
593
|
};
|
|
532
594
|
}
|
|
533
595
|
};
|
|
534
|
-
__name(
|
|
535
|
-
var
|
|
596
|
+
__name(_SearchResultVideo, "SearchResultVideo");
|
|
597
|
+
var SearchResultVideo = _SearchResultVideo;
|
|
598
|
+
var _SearchResultPlaylist = class _SearchResultPlaylist extends ISearchResult {
|
|
536
599
|
constructor(info) {
|
|
537
600
|
super(info);
|
|
538
601
|
__publicField(this, "type");
|
|
@@ -547,11 +610,20 @@ var SearchResultPlaylist = class extends ISearchResult {
|
|
|
547
610
|
};
|
|
548
611
|
}
|
|
549
612
|
};
|
|
550
|
-
__name(
|
|
613
|
+
__name(_SearchResultPlaylist, "SearchResultPlaylist");
|
|
614
|
+
var SearchResultPlaylist = _SearchResultPlaylist;
|
|
551
615
|
|
|
552
616
|
// src/struct/Song.ts
|
|
553
617
|
var _metadata2, _member2, _playlist;
|
|
554
|
-
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
|
+
*/
|
|
555
627
|
constructor(info, options = {}) {
|
|
556
628
|
__publicField(this, "source");
|
|
557
629
|
__privateAdd(this, _metadata2, void 0);
|
|
@@ -625,6 +697,11 @@ var _Song = class {
|
|
|
625
697
|
this.chapters = details.chapters || [];
|
|
626
698
|
this.reposts = 0;
|
|
627
699
|
}
|
|
700
|
+
/**
|
|
701
|
+
* Patch data from other source
|
|
702
|
+
* @param {OtherSongInfo} info Video info
|
|
703
|
+
* @private
|
|
704
|
+
*/
|
|
628
705
|
_patchOther(info) {
|
|
629
706
|
this.id = info.id;
|
|
630
707
|
this.name = info.title || info.name;
|
|
@@ -655,6 +732,10 @@ var _Song = class {
|
|
|
655
732
|
this.age_restricted = info.age_restricted || !!info.age_limit && parseNumber(info.age_limit) >= 18;
|
|
656
733
|
this.chapters = info.chapters || [];
|
|
657
734
|
}
|
|
735
|
+
/**
|
|
736
|
+
* The playlist added this song
|
|
737
|
+
* @type {Playlist?}
|
|
738
|
+
*/
|
|
658
739
|
get playlist() {
|
|
659
740
|
return __privateGet(this, _playlist);
|
|
660
741
|
}
|
|
@@ -664,6 +745,10 @@ var _Song = class {
|
|
|
664
745
|
__privateSet(this, _playlist, playlist);
|
|
665
746
|
this.member = playlist.member;
|
|
666
747
|
}
|
|
748
|
+
/**
|
|
749
|
+
* User requested.
|
|
750
|
+
* @type {Discord.GuildMember?}
|
|
751
|
+
*/
|
|
667
752
|
get member() {
|
|
668
753
|
return __privateGet(this, _member2);
|
|
669
754
|
}
|
|
@@ -671,6 +756,10 @@ var _Song = class {
|
|
|
671
756
|
if (isMemberInstance(member))
|
|
672
757
|
__privateSet(this, _member2, member);
|
|
673
758
|
}
|
|
759
|
+
/**
|
|
760
|
+
* User requested.
|
|
761
|
+
* @type {Discord.User?}
|
|
762
|
+
*/
|
|
674
763
|
get user() {
|
|
675
764
|
return this.member?.user;
|
|
676
765
|
}
|
|
@@ -681,48 +770,85 @@ var _Song = class {
|
|
|
681
770
|
__privateSet(this, _metadata2, metadata);
|
|
682
771
|
}
|
|
683
772
|
};
|
|
684
|
-
var Song = _Song;
|
|
685
|
-
__name(Song, "Song");
|
|
686
773
|
_metadata2 = new WeakMap();
|
|
687
774
|
_member2 = new WeakMap();
|
|
688
775
|
_playlist = new WeakMap();
|
|
776
|
+
__name(_Song, "Song");
|
|
777
|
+
var Song = _Song;
|
|
689
778
|
|
|
690
779
|
// src/core/DisTubeBase.ts
|
|
691
|
-
var
|
|
780
|
+
var _DisTubeBase = class _DisTubeBase {
|
|
692
781
|
constructor(distube) {
|
|
693
782
|
__publicField(this, "distube");
|
|
694
783
|
this.distube = distube;
|
|
695
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
|
+
*/
|
|
696
791
|
emit(eventName, ...args) {
|
|
697
792
|
return this.distube.emit(eventName, ...args);
|
|
698
793
|
}
|
|
794
|
+
/**
|
|
795
|
+
* Emit error event
|
|
796
|
+
* @param {Error} error error
|
|
797
|
+
* @param {Discord.BaseGuildTextChannel} [channel] Text channel where the error is encountered.
|
|
798
|
+
*/
|
|
699
799
|
emitError(error, channel) {
|
|
700
800
|
this.distube.emitError(error, channel);
|
|
701
801
|
}
|
|
802
|
+
/**
|
|
803
|
+
* The queue manager
|
|
804
|
+
* @type {QueueManager}
|
|
805
|
+
* @readonly
|
|
806
|
+
*/
|
|
702
807
|
get queues() {
|
|
703
808
|
return this.distube.queues;
|
|
704
809
|
}
|
|
810
|
+
/**
|
|
811
|
+
* The voice manager
|
|
812
|
+
* @type {DisTubeVoiceManager}
|
|
813
|
+
* @readonly
|
|
814
|
+
*/
|
|
705
815
|
get voices() {
|
|
706
816
|
return this.distube.voices;
|
|
707
817
|
}
|
|
818
|
+
/**
|
|
819
|
+
* Discord.js client
|
|
820
|
+
* @type {Discord.Client}
|
|
821
|
+
* @readonly
|
|
822
|
+
*/
|
|
708
823
|
get client() {
|
|
709
824
|
return this.distube.client;
|
|
710
825
|
}
|
|
826
|
+
/**
|
|
827
|
+
* DisTube options
|
|
828
|
+
* @type {DisTubeOptions}
|
|
829
|
+
* @readonly
|
|
830
|
+
*/
|
|
711
831
|
get options() {
|
|
712
832
|
return this.distube.options;
|
|
713
833
|
}
|
|
834
|
+
/**
|
|
835
|
+
* DisTube handler
|
|
836
|
+
* @type {DisTubeHandler}
|
|
837
|
+
* @readonly
|
|
838
|
+
*/
|
|
714
839
|
get handler() {
|
|
715
840
|
return this.distube.handler;
|
|
716
841
|
}
|
|
717
842
|
};
|
|
718
|
-
__name(
|
|
843
|
+
__name(_DisTubeBase, "DisTubeBase");
|
|
844
|
+
var DisTubeBase = _DisTubeBase;
|
|
719
845
|
|
|
720
846
|
// src/core/DisTubeVoice.ts
|
|
721
847
|
var import_discord = require("discord.js");
|
|
722
848
|
var import_tiny_typed_emitter = require("tiny-typed-emitter");
|
|
723
849
|
var import_voice = require("@discordjs/voice");
|
|
724
850
|
var _channel, _volume, _br, br_fn, _join, join_fn;
|
|
725
|
-
var
|
|
851
|
+
var _DisTubeVoice = class _DisTubeVoice extends import_tiny_typed_emitter.TypedEmitter {
|
|
726
852
|
constructor(voiceManager, channel) {
|
|
727
853
|
super();
|
|
728
854
|
__privateAdd(this, _br);
|
|
@@ -752,26 +878,37 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
|
|
|
752
878
|
this.emit("error", error);
|
|
753
879
|
});
|
|
754
880
|
this.connection.on(import_voice.VoiceConnectionStatus.Disconnected, (_, newState) => {
|
|
755
|
-
if (newState.reason === import_voice.VoiceConnectionDisconnectReason.Manual)
|
|
756
|
-
this.leave();
|
|
757
|
-
|
|
881
|
+
if (newState.reason === import_voice.VoiceConnectionDisconnectReason.Manual)
|
|
882
|
+
return this.leave();
|
|
883
|
+
if (newState.reason === import_voice.VoiceConnectionDisconnectReason.WebSocketClose && newState.closeCode === 4014) {
|
|
758
884
|
(0, import_voice.entersState)(this.connection, import_voice.VoiceConnectionStatus.Connecting, 5e3).catch(() => {
|
|
759
885
|
if (![import_voice.VoiceConnectionStatus.Ready, import_voice.VoiceConnectionStatus.Connecting].includes(this.connection.state.status)) {
|
|
760
886
|
this.leave();
|
|
761
887
|
}
|
|
762
888
|
});
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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"));
|
|
769
902
|
}
|
|
770
903
|
}).on(import_voice.VoiceConnectionStatus.Destroyed, () => {
|
|
771
904
|
this.leave();
|
|
772
905
|
}).on("error", () => void 0);
|
|
773
906
|
this.connection.subscribe(this.audioPlayer);
|
|
774
907
|
}
|
|
908
|
+
/**
|
|
909
|
+
* The voice channel id the bot is in
|
|
910
|
+
* @type {Snowflake?}
|
|
911
|
+
*/
|
|
775
912
|
get channelId() {
|
|
776
913
|
return this.connection?.joinConfig?.channelId ?? void 0;
|
|
777
914
|
}
|
|
@@ -811,6 +948,11 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
|
|
|
811
948
|
__privateSet(this, _channel, channel);
|
|
812
949
|
__privateMethod(this, _br, br_fn).call(this);
|
|
813
950
|
}
|
|
951
|
+
/**
|
|
952
|
+
* Join a voice channel with this connection
|
|
953
|
+
* @param {Discord.BaseGuildVoiceChannel} [channel] A voice channel
|
|
954
|
+
* @returns {Promise<DisTubeVoice>}
|
|
955
|
+
*/
|
|
814
956
|
async join(channel) {
|
|
815
957
|
const TIMEOUT = 3e4;
|
|
816
958
|
if (channel)
|
|
@@ -827,6 +969,10 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
|
|
|
827
969
|
}
|
|
828
970
|
return this;
|
|
829
971
|
}
|
|
972
|
+
/**
|
|
973
|
+
* Leave the voice channel of this connection
|
|
974
|
+
* @param {Error} [error] Optional, an error to emit with 'error' event.
|
|
975
|
+
*/
|
|
830
976
|
leave(error) {
|
|
831
977
|
this.stop(true);
|
|
832
978
|
if (!this.isDisconnected) {
|
|
@@ -837,9 +983,20 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
|
|
|
837
983
|
this.connection.destroy();
|
|
838
984
|
this.voices.remove(this.id);
|
|
839
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
|
+
*/
|
|
840
992
|
stop(force = false) {
|
|
841
993
|
this.audioPlayer.stop(force);
|
|
842
994
|
}
|
|
995
|
+
/**
|
|
996
|
+
* Play a readable stream
|
|
997
|
+
* @private
|
|
998
|
+
* @param {DisTubeStream} stream Readable stream
|
|
999
|
+
*/
|
|
843
1000
|
play(stream) {
|
|
844
1001
|
this.emittedError = false;
|
|
845
1002
|
stream.stream.on("error", (error) => {
|
|
@@ -853,7 +1010,8 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
|
|
|
853
1010
|
inlineVolume: true
|
|
854
1011
|
});
|
|
855
1012
|
this.volume = __privateGet(this, _volume);
|
|
856
|
-
this.audioPlayer.
|
|
1013
|
+
if (this.audioPlayer.state.status !== import_voice.AudioPlayerStatus.Paused)
|
|
1014
|
+
this.audioPlayer.play(this.audioResource);
|
|
857
1015
|
}
|
|
858
1016
|
set volume(volume) {
|
|
859
1017
|
if (typeof volume !== "number" || isNaN(volume)) {
|
|
@@ -868,6 +1026,10 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
|
|
|
868
1026
|
get volume() {
|
|
869
1027
|
return __privateGet(this, _volume);
|
|
870
1028
|
}
|
|
1029
|
+
/**
|
|
1030
|
+
* Playback duration of the audio resource in seconds
|
|
1031
|
+
* @type {number}
|
|
1032
|
+
*/
|
|
871
1033
|
get playbackDuration() {
|
|
872
1034
|
return (this.audioResource?.playbackDuration ?? 0) / 1e3;
|
|
873
1035
|
}
|
|
@@ -875,14 +1037,32 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
|
|
|
875
1037
|
this.audioPlayer.pause();
|
|
876
1038
|
}
|
|
877
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);
|
|
878
1045
|
this.audioPlayer.unpause();
|
|
879
1046
|
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Whether the bot is self-deafened
|
|
1049
|
+
* @type {boolean}
|
|
1050
|
+
*/
|
|
880
1051
|
get selfDeaf() {
|
|
881
1052
|
return this.connection.joinConfig.selfDeaf;
|
|
882
1053
|
}
|
|
1054
|
+
/**
|
|
1055
|
+
* Whether the bot is self-muted
|
|
1056
|
+
* @type {boolean}
|
|
1057
|
+
*/
|
|
883
1058
|
get selfMute() {
|
|
884
1059
|
return this.connection.joinConfig.selfMute;
|
|
885
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
|
+
*/
|
|
886
1066
|
setSelfDeaf(selfDeaf) {
|
|
887
1067
|
if (typeof selfDeaf !== "boolean") {
|
|
888
1068
|
throw new DisTubeError("INVALID_TYPE", "boolean", selfDeaf, "selfDeaf");
|
|
@@ -892,6 +1072,11 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
|
|
|
892
1072
|
selfDeaf
|
|
893
1073
|
});
|
|
894
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
|
+
*/
|
|
895
1080
|
setSelfMute(selfMute) {
|
|
896
1081
|
if (typeof selfMute !== "boolean") {
|
|
897
1082
|
throw new DisTubeError("INVALID_TYPE", "boolean", selfMute, "selfMute");
|
|
@@ -901,11 +1086,14 @@ var DisTubeVoice = class extends import_tiny_typed_emitter.TypedEmitter {
|
|
|
901
1086
|
selfMute
|
|
902
1087
|
});
|
|
903
1088
|
}
|
|
1089
|
+
/**
|
|
1090
|
+
* The voice state of this connection
|
|
1091
|
+
* @type {Discord.VoiceState?}
|
|
1092
|
+
*/
|
|
904
1093
|
get voiceState() {
|
|
905
1094
|
return this.channel?.guild?.members?.me?.voice;
|
|
906
1095
|
}
|
|
907
1096
|
};
|
|
908
|
-
__name(DisTubeVoice, "DisTubeVoice");
|
|
909
1097
|
_channel = new WeakMap();
|
|
910
1098
|
_volume = new WeakMap();
|
|
911
1099
|
_br = new WeakSet();
|
|
@@ -922,11 +1110,104 @@ join_fn = /* @__PURE__ */ __name(function(channel) {
|
|
|
922
1110
|
group: channel.client.user?.id
|
|
923
1111
|
});
|
|
924
1112
|
}, "#join");
|
|
1113
|
+
__name(_DisTubeVoice, "DisTubeVoice");
|
|
1114
|
+
var DisTubeVoice = _DisTubeVoice;
|
|
1115
|
+
|
|
1116
|
+
// src/core/DisTubeStream.ts
|
|
1117
|
+
var import_prism_media = require("prism-media");
|
|
1118
|
+
var import_voice2 = require("@discordjs/voice");
|
|
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");
|
|
1160
|
+
else
|
|
1161
|
+
args.push("s16le");
|
|
1162
|
+
if (typeof options.seek === "number" && options.seek > 0)
|
|
1163
|
+
args.unshift("-ss", options.seek.toString());
|
|
1164
|
+
if (Array.isArray(options.ffmpegArgs) && options.ffmpegArgs.length)
|
|
1165
|
+
args.push(...options.ffmpegArgs);
|
|
1166
|
+
this.stream = new import_prism_media.FFmpeg({ args, shell: false });
|
|
1167
|
+
this.stream._readableState && (this.stream._readableState.highWaterMark = 1 << 25);
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Create a stream from ytdl video formats
|
|
1171
|
+
* @param {ytdl.videoFormat[]} formats ytdl video formats
|
|
1172
|
+
* @param {StreamOptions} options options
|
|
1173
|
+
* @returns {DisTubeStream}
|
|
1174
|
+
* @private
|
|
1175
|
+
*/
|
|
1176
|
+
static YouTube(formats, options = {}) {
|
|
1177
|
+
if (!formats || !formats.length)
|
|
1178
|
+
throw new DisTubeError("UNAVAILABLE_VIDEO");
|
|
1179
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
1180
|
+
throw new DisTubeError("INVALID_TYPE", "object", options, "options");
|
|
1181
|
+
}
|
|
1182
|
+
const bestFormat = chooseBestVideoFormat(formats, options.isLive);
|
|
1183
|
+
if (!bestFormat)
|
|
1184
|
+
throw new DisTubeError("UNPLAYABLE_FORMATS");
|
|
1185
|
+
return new _DisTubeStream(bestFormat.url, options);
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Create a stream from a stream url
|
|
1189
|
+
* @param {string} url stream url
|
|
1190
|
+
* @param {StreamOptions} options options
|
|
1191
|
+
* @returns {DisTubeStream}
|
|
1192
|
+
* @private
|
|
1193
|
+
*/
|
|
1194
|
+
static DirectLink(url, options = {}) {
|
|
1195
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
1196
|
+
throw new DisTubeError("INVALID_TYPE", "object", options, "options");
|
|
1197
|
+
}
|
|
1198
|
+
if (typeof url !== "string" || !isURL(url)) {
|
|
1199
|
+
throw new DisTubeError("INVALID_TYPE", "an URL", url);
|
|
1200
|
+
}
|
|
1201
|
+
return new _DisTubeStream(url, options);
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
__name(_DisTubeStream, "DisTubeStream");
|
|
1205
|
+
var DisTubeStream = _DisTubeStream;
|
|
925
1206
|
|
|
926
1207
|
// src/core/DisTubeHandler.ts
|
|
927
1208
|
var import_ytpl = __toESM(require("@distube/ytpl"));
|
|
928
1209
|
var import_ytdl_core = __toESM(require("@distube/ytdl-core"));
|
|
929
|
-
var
|
|
1210
|
+
var _DisTubeHandler = class _DisTubeHandler extends DisTubeBase {
|
|
930
1211
|
constructor(distube) {
|
|
931
1212
|
super(distube);
|
|
932
1213
|
const client = this.client;
|
|
@@ -976,11 +1257,23 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
976
1257
|
}
|
|
977
1258
|
return options;
|
|
978
1259
|
}
|
|
1260
|
+
/**
|
|
1261
|
+
* @param {string} url url
|
|
1262
|
+
* @param {boolean} [basic=false] getBasicInfo?
|
|
1263
|
+
* @returns {Promise<ytdl.videoInfo>}
|
|
1264
|
+
*/
|
|
979
1265
|
getYouTubeInfo(url, basic = false) {
|
|
980
1266
|
if (basic)
|
|
981
1267
|
return import_ytdl_core.default.getBasicInfo(url, this.ytdlOptions);
|
|
982
1268
|
return import_ytdl_core.default.getInfo(url, this.ytdlOptions);
|
|
983
1269
|
}
|
|
1270
|
+
/**
|
|
1271
|
+
* Resolve a url or a supported object to a {@link Song} or {@link Playlist}
|
|
1272
|
+
* @param {string|Song|SearchResult|Playlist} song URL | {@link Song}| {@link SearchResult} | {@link Playlist}
|
|
1273
|
+
* @param {ResolveOptions} [options] Optional options
|
|
1274
|
+
* @returns {Promise<Song|Playlist|null>} Resolved
|
|
1275
|
+
* @throws {DisTubeError}
|
|
1276
|
+
*/
|
|
984
1277
|
async resolve(song, options = {}) {
|
|
985
1278
|
if (song instanceof Song || song instanceof Playlist) {
|
|
986
1279
|
if ("metadata" in options)
|
|
@@ -1011,6 +1304,12 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1011
1304
|
}
|
|
1012
1305
|
throw new DisTubeError("CANNOT_RESOLVE_SONG", song);
|
|
1013
1306
|
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Resolve Song[] or YouTube playlist url to a Playlist
|
|
1309
|
+
* @param {Playlist|Song[]|string} playlist Resolvable playlist
|
|
1310
|
+
* @param {ResolvePlaylistOptions} options Optional options
|
|
1311
|
+
* @returns {Promise<Playlist>}
|
|
1312
|
+
*/
|
|
1014
1313
|
async resolvePlaylist(playlist, options = {}) {
|
|
1015
1314
|
const { member, source, metadata } = { source: "youtube", ...options };
|
|
1016
1315
|
if (playlist instanceof Playlist) {
|
|
@@ -1037,6 +1336,13 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1037
1336
|
}
|
|
1038
1337
|
return new Playlist(playlist, { member, properties: { source }, metadata });
|
|
1039
1338
|
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Search for a song, fire {@link DisTube#event:error} if not found.
|
|
1341
|
+
* @param {Discord.Message} message The original message from an user
|
|
1342
|
+
* @param {string} query The query string
|
|
1343
|
+
* @returns {Promise<SearchResult?>} Song info
|
|
1344
|
+
* @throws {DisTubeError}
|
|
1345
|
+
*/
|
|
1040
1346
|
async searchSong(message, query) {
|
|
1041
1347
|
if (!isMessageInstance(message))
|
|
1042
1348
|
throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
|
|
@@ -1047,7 +1353,7 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1047
1353
|
const limit = this.options.searchSongs > 1 ? this.options.searchSongs : 1;
|
|
1048
1354
|
const results = await this.distube.search(query, {
|
|
1049
1355
|
limit,
|
|
1050
|
-
safeSearch: this.options.nsfw ? false : !message.channel
|
|
1356
|
+
safeSearch: this.options.nsfw ? false : !isNsfwChannel(message.channel)
|
|
1051
1357
|
}).catch(() => {
|
|
1052
1358
|
if (!this.emit("searchNoResult", message, query)) {
|
|
1053
1359
|
console.warn("searchNoResult event does not have any listeners! Emits `error` event instead.");
|
|
@@ -1058,6 +1364,17 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1058
1364
|
return null;
|
|
1059
1365
|
return this.createSearchMessageCollector(message, results, query);
|
|
1060
1366
|
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Create a message collector for selecting search results.
|
|
1369
|
+
*
|
|
1370
|
+
* Needed events: {@link DisTube#event:searchResult}, {@link DisTube#event:searchCancel},
|
|
1371
|
+
* {@link DisTube#event:searchInvalidAnswer}, {@link DisTube#event:searchDone}.
|
|
1372
|
+
* @param {Discord.Message} message The original message from an user
|
|
1373
|
+
* @param {Array<SearchResult|Song|Playlist>} results The search results
|
|
1374
|
+
* @param {string?} [query] The query string
|
|
1375
|
+
* @returns {Promise<SearchResult|Song|Playlist|null>} Selected result
|
|
1376
|
+
* @throws {DisTubeError}
|
|
1377
|
+
*/
|
|
1061
1378
|
async createSearchMessageCollector(message, results, query) {
|
|
1062
1379
|
if (!isMessageInstance(message))
|
|
1063
1380
|
throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
|
|
@@ -1110,19 +1427,26 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1110
1427
|
}
|
|
1111
1428
|
return result;
|
|
1112
1429
|
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Play or add a {@link Playlist} to the queue.
|
|
1432
|
+
* @param {Discord.BaseGuildVoiceChannel} voiceChannel A voice channel
|
|
1433
|
+
* @param {Playlist|string} playlist A YouTube playlist url | a Playlist
|
|
1434
|
+
* @param {PlayHandlerOptions} [options] Optional options
|
|
1435
|
+
* @returns {Promise<void>}
|
|
1436
|
+
* @throws {DisTubeError}
|
|
1437
|
+
*/
|
|
1113
1438
|
async playPlaylist(voiceChannel, playlist, options = {}) {
|
|
1114
1439
|
const { textChannel, skip } = { skip: false, ...options };
|
|
1115
1440
|
const position = Number(options.position) || (skip ? 1 : 0);
|
|
1116
1441
|
if (!(playlist instanceof Playlist))
|
|
1117
1442
|
throw new DisTubeError("INVALID_TYPE", "Playlist", playlist, "playlist");
|
|
1118
1443
|
const queue = this.queues.get(voiceChannel);
|
|
1119
|
-
|
|
1444
|
+
const isNsfw = isNsfwChannel(queue?.textChannel || textChannel);
|
|
1445
|
+
if (!this.options.nsfw && !isNsfw)
|
|
1120
1446
|
playlist.songs = playlist.songs.filter((s) => !s.age_restricted);
|
|
1121
|
-
}
|
|
1122
1447
|
if (!playlist.songs.length) {
|
|
1123
|
-
if (!this.options.nsfw && !
|
|
1448
|
+
if (!this.options.nsfw && !isNsfw)
|
|
1124
1449
|
throw new DisTubeError("EMPTY_FILTERED_PLAYLIST");
|
|
1125
|
-
}
|
|
1126
1450
|
throw new DisTubeError("EMPTY_PLAYLIST");
|
|
1127
1451
|
}
|
|
1128
1452
|
if (queue) {
|
|
@@ -1142,13 +1466,21 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1142
1466
|
}
|
|
1143
1467
|
}
|
|
1144
1468
|
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Play or add a {@link Song} to the queue.
|
|
1471
|
+
* @param {Discord.BaseGuildVoiceChannel} voiceChannel A voice channel
|
|
1472
|
+
* @param {Song} song A YouTube playlist url | a Playlist
|
|
1473
|
+
* @param {PlayHandlerOptions} [options] Optional options
|
|
1474
|
+
* @returns {Promise<void>}
|
|
1475
|
+
* @throws {DisTubeError}
|
|
1476
|
+
*/
|
|
1145
1477
|
async playSong(voiceChannel, song, options = {}) {
|
|
1146
1478
|
if (!(song instanceof Song))
|
|
1147
1479
|
throw new DisTubeError("INVALID_TYPE", "Song", song, "song");
|
|
1148
1480
|
const { textChannel, skip } = { skip: false, ...options };
|
|
1149
1481
|
const position = Number(options.position) || (skip ? 1 : 0);
|
|
1150
1482
|
const queue = this.queues.get(voiceChannel);
|
|
1151
|
-
if (!this.options.nsfw && song.age_restricted && !(queue?.textChannel || textChannel)
|
|
1483
|
+
if (!this.options.nsfw && song.age_restricted && !isNsfwChannel(queue?.textChannel || textChannel)) {
|
|
1152
1484
|
throw new DisTubeError("NON_NSFW");
|
|
1153
1485
|
}
|
|
1154
1486
|
if (queue) {
|
|
@@ -1168,12 +1500,35 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1168
1500
|
}
|
|
1169
1501
|
}
|
|
1170
1502
|
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Get {@link Song}'s stream info and attach it to the song.
|
|
1505
|
+
* @param {Song} song A Song
|
|
1506
|
+
*/
|
|
1507
|
+
async attachStreamInfo(song) {
|
|
1508
|
+
const { url, source, formats, streamURL, isLive } = song;
|
|
1509
|
+
if (source === "youtube") {
|
|
1510
|
+
if (!formats || !chooseBestVideoFormat(formats, isLive)) {
|
|
1511
|
+
song._patchYouTube(await this.handler.getYouTubeInfo(url));
|
|
1512
|
+
}
|
|
1513
|
+
} else if (!streamURL) {
|
|
1514
|
+
for (const plugin of [...this.distube.extractorPlugins, ...this.distube.customPlugins]) {
|
|
1515
|
+
if (await plugin.validate(url)) {
|
|
1516
|
+
const info = [plugin.getStreamURL(url), plugin.getRelatedSongs(url)];
|
|
1517
|
+
const result = await Promise.all(info);
|
|
1518
|
+
song.streamURL = result[0];
|
|
1519
|
+
song.related = result[1];
|
|
1520
|
+
break;
|
|
1521
|
+
}
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1171
1525
|
};
|
|
1172
|
-
__name(
|
|
1526
|
+
__name(_DisTubeHandler, "DisTubeHandler");
|
|
1527
|
+
var DisTubeHandler = _DisTubeHandler;
|
|
1173
1528
|
|
|
1174
1529
|
// src/core/DisTubeOptions.ts
|
|
1175
1530
|
var _validateOptions, validateOptions_fn;
|
|
1176
|
-
var
|
|
1531
|
+
var _Options = class _Options {
|
|
1177
1532
|
constructor(options) {
|
|
1178
1533
|
__privateAdd(this, _validateOptions);
|
|
1179
1534
|
__publicField(this, "plugins");
|
|
@@ -1222,7 +1577,6 @@ var Options = class {
|
|
|
1222
1577
|
__privateMethod(this, _validateOptions, validateOptions_fn).call(this);
|
|
1223
1578
|
}
|
|
1224
1579
|
};
|
|
1225
|
-
__name(Options, "Options");
|
|
1226
1580
|
_validateOptions = new WeakSet();
|
|
1227
1581
|
validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
|
|
1228
1582
|
if (typeof options.emitNewSongOnly !== "boolean") {
|
|
@@ -1303,95 +1657,34 @@ validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
|
|
|
1303
1657
|
throw new DisTubeError("INVALID_TYPE", "boolean", options.directLink, "DisTubeOptions.directLink");
|
|
1304
1658
|
}
|
|
1305
1659
|
}, "#validateOptions");
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
var import_prism_media = require("prism-media");
|
|
1309
|
-
var import_voice2 = require("@discordjs/voice");
|
|
1310
|
-
var chooseBestVideoFormat = /* @__PURE__ */ __name((formats, isLive = false) => {
|
|
1311
|
-
let filter = /* @__PURE__ */ __name((format) => format.hasAudio, "filter");
|
|
1312
|
-
if (isLive)
|
|
1313
|
-
filter = /* @__PURE__ */ __name((format) => format.hasAudio && format.isHLS, "filter");
|
|
1314
|
-
formats = formats.filter(filter).sort((a, b) => Number(b.audioBitrate) - Number(a.audioBitrate) || Number(a.bitrate) - Number(b.bitrate));
|
|
1315
|
-
return formats.find((format) => !format.hasVideo) || formats.sort((a, b) => Number(a.bitrate) - Number(b.bitrate))[0];
|
|
1316
|
-
}, "chooseBestVideoFormat");
|
|
1317
|
-
var DisTubeStream = class {
|
|
1318
|
-
constructor(url, options) {
|
|
1319
|
-
__publicField(this, "type");
|
|
1320
|
-
__publicField(this, "stream");
|
|
1321
|
-
__publicField(this, "url");
|
|
1322
|
-
this.url = url;
|
|
1323
|
-
this.type = !options.type ? import_voice2.StreamType.OggOpus : import_voice2.StreamType.Raw;
|
|
1324
|
-
const args = [
|
|
1325
|
-
"-reconnect",
|
|
1326
|
-
"1",
|
|
1327
|
-
"-reconnect_streamed",
|
|
1328
|
-
"1",
|
|
1329
|
-
"-reconnect_delay_max",
|
|
1330
|
-
"5",
|
|
1331
|
-
"-i",
|
|
1332
|
-
url,
|
|
1333
|
-
"-analyzeduration",
|
|
1334
|
-
"0",
|
|
1335
|
-
"-loglevel",
|
|
1336
|
-
"0",
|
|
1337
|
-
"-ar",
|
|
1338
|
-
"48000",
|
|
1339
|
-
"-ac",
|
|
1340
|
-
"2",
|
|
1341
|
-
"-f"
|
|
1342
|
-
];
|
|
1343
|
-
if (!options.type) {
|
|
1344
|
-
args.push("opus", "-acodec", "libopus");
|
|
1345
|
-
} else {
|
|
1346
|
-
args.push("s16le");
|
|
1347
|
-
}
|
|
1348
|
-
if (typeof options.seek === "number" && options.seek > 0) {
|
|
1349
|
-
args.unshift("-ss", options.seek.toString());
|
|
1350
|
-
}
|
|
1351
|
-
if (Array.isArray(options.ffmpegArgs)) {
|
|
1352
|
-
args.push(...options.ffmpegArgs);
|
|
1353
|
-
}
|
|
1354
|
-
this.stream = new import_prism_media.FFmpeg({ args, shell: false });
|
|
1355
|
-
this.stream._readableState && (this.stream._readableState.highWaterMark = 1 << 25);
|
|
1356
|
-
}
|
|
1357
|
-
static YouTube(formats, options = {}) {
|
|
1358
|
-
if (!formats || !formats.length)
|
|
1359
|
-
throw new DisTubeError("UNAVAILABLE_VIDEO");
|
|
1360
|
-
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
1361
|
-
throw new DisTubeError("INVALID_TYPE", "object", options, "options");
|
|
1362
|
-
}
|
|
1363
|
-
const bestFormat = chooseBestVideoFormat(formats, options.isLive);
|
|
1364
|
-
if (!bestFormat)
|
|
1365
|
-
throw new DisTubeError("UNPLAYABLE_FORMATS");
|
|
1366
|
-
return new DisTubeStream(bestFormat.url, options);
|
|
1367
|
-
}
|
|
1368
|
-
static DirectLink(url, options = {}) {
|
|
1369
|
-
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
1370
|
-
throw new DisTubeError("INVALID_TYPE", "object", options, "options");
|
|
1371
|
-
}
|
|
1372
|
-
if (typeof url !== "string" || !isURL(url)) {
|
|
1373
|
-
throw new DisTubeError("INVALID_TYPE", "an URL", url);
|
|
1374
|
-
}
|
|
1375
|
-
return new DisTubeStream(url, options);
|
|
1376
|
-
}
|
|
1377
|
-
};
|
|
1378
|
-
__name(DisTubeStream, "DisTubeStream");
|
|
1660
|
+
__name(_Options, "Options");
|
|
1661
|
+
var Options = _Options;
|
|
1379
1662
|
|
|
1380
1663
|
// src/core/manager/BaseManager.ts
|
|
1381
1664
|
var import_discord2 = require("discord.js");
|
|
1382
|
-
var
|
|
1665
|
+
var _BaseManager = class _BaseManager extends DisTubeBase {
|
|
1383
1666
|
constructor() {
|
|
1384
1667
|
super(...arguments);
|
|
1668
|
+
/**
|
|
1669
|
+
* The collection of items for this manager.
|
|
1670
|
+
* @type {Collection}
|
|
1671
|
+
* @name BaseManager#collection
|
|
1672
|
+
*/
|
|
1385
1673
|
__publicField(this, "collection", new import_discord2.Collection());
|
|
1386
1674
|
}
|
|
1675
|
+
/**
|
|
1676
|
+
* The size of the collection.
|
|
1677
|
+
* @type {number}
|
|
1678
|
+
*/
|
|
1387
1679
|
get size() {
|
|
1388
1680
|
return this.collection.size;
|
|
1389
1681
|
}
|
|
1390
1682
|
};
|
|
1391
|
-
__name(
|
|
1683
|
+
__name(_BaseManager, "BaseManager");
|
|
1684
|
+
var BaseManager = _BaseManager;
|
|
1392
1685
|
|
|
1393
1686
|
// src/core/manager/GuildIdManager.ts
|
|
1394
|
-
var
|
|
1687
|
+
var _GuildIdManager = class _GuildIdManager extends BaseManager {
|
|
1395
1688
|
add(idOrInstance, data) {
|
|
1396
1689
|
const id = resolveGuildId(idOrInstance);
|
|
1397
1690
|
const existing = this.get(id);
|
|
@@ -1409,11 +1702,30 @@ var GuildIdManager = class extends BaseManager {
|
|
|
1409
1702
|
return this.collection.has(resolveGuildId(idOrInstance));
|
|
1410
1703
|
}
|
|
1411
1704
|
};
|
|
1412
|
-
__name(
|
|
1705
|
+
__name(_GuildIdManager, "GuildIdManager");
|
|
1706
|
+
var GuildIdManager = _GuildIdManager;
|
|
1413
1707
|
|
|
1414
1708
|
// src/core/manager/DisTubeVoiceManager.ts
|
|
1415
1709
|
var import_voice3 = require("@discordjs/voice");
|
|
1416
|
-
var
|
|
1710
|
+
var _DisTubeVoiceManager = class _DisTubeVoiceManager extends GuildIdManager {
|
|
1711
|
+
/**
|
|
1712
|
+
* Get a {@link DisTubeVoice}.
|
|
1713
|
+
* @method get
|
|
1714
|
+
* @memberof DisTubeVoiceManager#
|
|
1715
|
+
* @param {GuildIdResolvable} guild The queue resolvable to resolve
|
|
1716
|
+
* @returns {DisTubeVoice?}
|
|
1717
|
+
*/
|
|
1718
|
+
/**
|
|
1719
|
+
* Collection of {@link DisTubeVoice}.
|
|
1720
|
+
* @name DisTubeVoiceManager#collection
|
|
1721
|
+
* @type {Discord.Collection<string, DisTubeVoice>}
|
|
1722
|
+
*/
|
|
1723
|
+
/**
|
|
1724
|
+
* Create a {@link DisTubeVoice}
|
|
1725
|
+
* @param {Discord.BaseGuildVoiceChannel} channel A voice channel to join
|
|
1726
|
+
* @returns {DisTubeVoice}
|
|
1727
|
+
* @private
|
|
1728
|
+
*/
|
|
1417
1729
|
create(channel) {
|
|
1418
1730
|
const existing = this.get(channel.guildId);
|
|
1419
1731
|
if (existing) {
|
|
@@ -1422,12 +1734,21 @@ var DisTubeVoiceManager = class extends GuildIdManager {
|
|
|
1422
1734
|
}
|
|
1423
1735
|
return new DisTubeVoice(this, channel);
|
|
1424
1736
|
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Join a voice channel
|
|
1739
|
+
* @param {Discord.BaseGuildVoiceChannel} channel A voice channel to join
|
|
1740
|
+
* @returns {Promise<DisTubeVoice>}
|
|
1741
|
+
*/
|
|
1425
1742
|
join(channel) {
|
|
1426
1743
|
const existing = this.get(channel.guildId);
|
|
1427
1744
|
if (existing)
|
|
1428
1745
|
return existing.join(channel);
|
|
1429
1746
|
return this.create(channel).join();
|
|
1430
1747
|
}
|
|
1748
|
+
/**
|
|
1749
|
+
* Leave the connected voice channel in a guild
|
|
1750
|
+
* @param {GuildIdResolvable} guild Queue Resolvable
|
|
1751
|
+
*/
|
|
1431
1752
|
leave(guild) {
|
|
1432
1753
|
const voice = this.get(guild);
|
|
1433
1754
|
if (voice) {
|
|
@@ -1440,104 +1761,191 @@ var DisTubeVoiceManager = class extends GuildIdManager {
|
|
|
1440
1761
|
}
|
|
1441
1762
|
}
|
|
1442
1763
|
};
|
|
1443
|
-
__name(
|
|
1764
|
+
__name(_DisTubeVoiceManager, "DisTubeVoiceManager");
|
|
1765
|
+
var DisTubeVoiceManager = _DisTubeVoiceManager;
|
|
1444
1766
|
|
|
1445
1767
|
// src/core/manager/FilterManager.ts
|
|
1446
|
-
var
|
|
1447
|
-
var
|
|
1768
|
+
var _resolve, resolve_fn, _apply, apply_fn, _removeFn, removeFn_fn;
|
|
1769
|
+
var _FilterManager = class _FilterManager extends BaseManager {
|
|
1448
1770
|
constructor(queue) {
|
|
1449
1771
|
super(queue.distube);
|
|
1450
|
-
__privateAdd(this,
|
|
1451
|
-
__privateAdd(this, _resolveName);
|
|
1452
|
-
__privateAdd(this, _resolveValue);
|
|
1772
|
+
__privateAdd(this, _resolve);
|
|
1453
1773
|
__privateAdd(this, _apply);
|
|
1774
|
+
__privateAdd(this, _removeFn);
|
|
1775
|
+
/**
|
|
1776
|
+
* Collection of {@link Filter}.
|
|
1777
|
+
* @name FilterManager#collection
|
|
1778
|
+
* @type {Discord.Collection<string, DisTubeVoice>}
|
|
1779
|
+
*/
|
|
1454
1780
|
__publicField(this, "queue");
|
|
1455
1781
|
this.queue = queue;
|
|
1456
1782
|
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Enable a filter or multiple filters to the manager
|
|
1785
|
+
* @param {FilterResolvable|FilterResolvable[]} filterOrFilters The filter or filters to enable
|
|
1786
|
+
* @param {boolean} [override=false] Wether or not override the applied filter with new filter value
|
|
1787
|
+
* @returns {FilterManager}
|
|
1788
|
+
*/
|
|
1457
1789
|
add(filterOrFilters, override = false) {
|
|
1458
1790
|
if (Array.isArray(filterOrFilters)) {
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
if (
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
}
|
|
1469
|
-
return unique;
|
|
1470
|
-
}, []).reverse();
|
|
1471
|
-
return this.set([...this.collection.values(), ...newFilters]);
|
|
1791
|
+
for (const filter of filterOrFilters) {
|
|
1792
|
+
const f = __privateMethod(this, _resolve, resolve_fn).call(this, filter);
|
|
1793
|
+
if (override || !this.has(f))
|
|
1794
|
+
this.collection.set(f.name, f);
|
|
1795
|
+
}
|
|
1796
|
+
} else {
|
|
1797
|
+
const f = __privateMethod(this, _resolve, resolve_fn).call(this, filterOrFilters);
|
|
1798
|
+
if (override || !this.has(f))
|
|
1799
|
+
this.collection.set(f.name, f);
|
|
1472
1800
|
}
|
|
1473
|
-
|
|
1801
|
+
__privateMethod(this, _apply, apply_fn).call(this);
|
|
1802
|
+
return this;
|
|
1474
1803
|
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Clear enabled filters of the manager
|
|
1806
|
+
* @returns {FilterManager}
|
|
1807
|
+
*/
|
|
1475
1808
|
clear() {
|
|
1476
1809
|
return this.set([]);
|
|
1477
1810
|
}
|
|
1811
|
+
/**
|
|
1812
|
+
* Set the filters applied to the manager
|
|
1813
|
+
* @param {FilterResolvable[]} filters The filters to apply
|
|
1814
|
+
* @returns {FilterManager}
|
|
1815
|
+
*/
|
|
1478
1816
|
set(filters) {
|
|
1817
|
+
if (!Array.isArray(filters))
|
|
1818
|
+
throw new DisTubeError("INVALID_TYPE", "Array<FilterResolvable>", filters, "filters");
|
|
1479
1819
|
this.collection.clear();
|
|
1480
|
-
for (const
|
|
1481
|
-
const
|
|
1482
|
-
this.collection.set(
|
|
1820
|
+
for (const f of filters) {
|
|
1821
|
+
const filter = __privateMethod(this, _resolve, resolve_fn).call(this, f);
|
|
1822
|
+
this.collection.set(filter.name, filter);
|
|
1483
1823
|
}
|
|
1484
1824
|
__privateMethod(this, _apply, apply_fn).call(this);
|
|
1485
1825
|
return this;
|
|
1486
1826
|
}
|
|
1827
|
+
/**
|
|
1828
|
+
* Disable a filter or multiple filters
|
|
1829
|
+
* @param {FilterResolvable|FilterResolvable[]} filterOrFilters The filter or filters to disable
|
|
1830
|
+
* @returns {FilterManager}
|
|
1831
|
+
*/
|
|
1487
1832
|
remove(filterOrFilters) {
|
|
1488
|
-
const remove = /* @__PURE__ */ __name((f) => this.collection.delete(__privateMethod(this, _resolveName, resolveName_fn).call(this, __privateMethod(this, _validate, validate_fn).call(this, f))), "remove");
|
|
1489
1833
|
if (Array.isArray(filterOrFilters))
|
|
1490
|
-
filterOrFilters.map(
|
|
1834
|
+
filterOrFilters.map((f) => __privateMethod(this, _removeFn, removeFn_fn).call(this, f));
|
|
1491
1835
|
else
|
|
1492
|
-
|
|
1836
|
+
__privateMethod(this, _removeFn, removeFn_fn).call(this, filterOrFilters);
|
|
1493
1837
|
__privateMethod(this, _apply, apply_fn).call(this);
|
|
1494
1838
|
return this;
|
|
1495
1839
|
}
|
|
1840
|
+
/**
|
|
1841
|
+
* Check whether a filter enabled or not
|
|
1842
|
+
* @param {FilterResolvable} filter The filter to check
|
|
1843
|
+
* @returns {boolean}
|
|
1844
|
+
*/
|
|
1496
1845
|
has(filter) {
|
|
1497
|
-
return this.collection.has(__privateMethod(this,
|
|
1846
|
+
return this.collection.has(typeof filter === "string" ? filter : __privateMethod(this, _resolve, resolve_fn).call(this, filter).name);
|
|
1498
1847
|
}
|
|
1848
|
+
/**
|
|
1849
|
+
* Array of enabled filter names
|
|
1850
|
+
* @type {Array<string>}
|
|
1851
|
+
* @readonly
|
|
1852
|
+
*/
|
|
1499
1853
|
get names() {
|
|
1500
|
-
return this.collection.
|
|
1854
|
+
return [...this.collection.keys()];
|
|
1501
1855
|
}
|
|
1856
|
+
/**
|
|
1857
|
+
* Array of enabled filters
|
|
1858
|
+
* @type {Array<Filter>}
|
|
1859
|
+
* @readonly
|
|
1860
|
+
*/
|
|
1502
1861
|
get values() {
|
|
1503
|
-
return this.collection.
|
|
1862
|
+
return [...this.collection.values()];
|
|
1863
|
+
}
|
|
1864
|
+
get ffmpegArgs() {
|
|
1865
|
+
return this.size ? ["-af", this.values.map((f) => f.value).join(",")] : [];
|
|
1504
1866
|
}
|
|
1505
1867
|
toString() {
|
|
1506
1868
|
return this.names.toString();
|
|
1507
1869
|
}
|
|
1508
1870
|
};
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
if (typeof filter === "string" && Object.prototype.hasOwnProperty.call(this.distube.filters, filter) || typeof filter === "object" && typeof filter.name === "string" && typeof filter.value === "string") {
|
|
1871
|
+
_resolve = new WeakSet();
|
|
1872
|
+
resolve_fn = /* @__PURE__ */ __name(function(filter) {
|
|
1873
|
+
if (typeof filter === "object" && typeof filter.name === "string" && typeof filter.value === "string") {
|
|
1513
1874
|
return filter;
|
|
1514
1875
|
}
|
|
1876
|
+
if (typeof filter === "string" && Object.prototype.hasOwnProperty.call(this.distube.filters, filter)) {
|
|
1877
|
+
return {
|
|
1878
|
+
name: filter,
|
|
1879
|
+
value: this.distube.filters[filter]
|
|
1880
|
+
};
|
|
1881
|
+
}
|
|
1515
1882
|
throw new DisTubeError("INVALID_TYPE", "FilterResolvable", filter, "filter");
|
|
1516
|
-
}, "#
|
|
1517
|
-
_resolveName = new WeakSet();
|
|
1518
|
-
resolveName_fn = /* @__PURE__ */ __name(function(filter) {
|
|
1519
|
-
return typeof filter === "string" ? filter : filter.name;
|
|
1520
|
-
}, "#resolveName");
|
|
1521
|
-
_resolveValue = new WeakSet();
|
|
1522
|
-
resolveValue_fn = /* @__PURE__ */ __name(function(filter) {
|
|
1523
|
-
return typeof filter === "string" ? this.distube.filters[filter] : filter.value;
|
|
1524
|
-
}, "#resolveValue");
|
|
1883
|
+
}, "#resolve");
|
|
1525
1884
|
_apply = new WeakSet();
|
|
1526
1885
|
apply_fn = /* @__PURE__ */ __name(function() {
|
|
1527
1886
|
this.queue.beginTime = this.queue.currentTime;
|
|
1528
1887
|
this.queues.playSong(this.queue);
|
|
1529
1888
|
}, "#apply");
|
|
1889
|
+
_removeFn = new WeakSet();
|
|
1890
|
+
removeFn_fn = /* @__PURE__ */ __name(function(f) {
|
|
1891
|
+
return this.collection.delete(__privateMethod(this, _resolve, resolve_fn).call(this, f).name);
|
|
1892
|
+
}, "#removeFn");
|
|
1893
|
+
__name(_FilterManager, "FilterManager");
|
|
1894
|
+
var FilterManager = _FilterManager;
|
|
1530
1895
|
|
|
1531
1896
|
// src/core/manager/QueueManager.ts
|
|
1532
|
-
var _voiceEventHandler, voiceEventHandler_fn, _handleSongFinish, handleSongFinish_fn, _handlePlayingError, handlePlayingError_fn
|
|
1533
|
-
var
|
|
1897
|
+
var _voiceEventHandler, voiceEventHandler_fn, _emitPlaySong, emitPlaySong_fn, _handleSongFinish, handleSongFinish_fn, _handlePlayingError, handlePlayingError_fn;
|
|
1898
|
+
var _QueueManager = class _QueueManager extends GuildIdManager {
|
|
1534
1899
|
constructor() {
|
|
1535
1900
|
super(...arguments);
|
|
1901
|
+
/**
|
|
1902
|
+
* Get a Queue from this QueueManager.
|
|
1903
|
+
* @method get
|
|
1904
|
+
* @memberof QueueManager#
|
|
1905
|
+
* @param {GuildIdResolvable} guild Resolvable thing from a guild
|
|
1906
|
+
* @returns {Queue?}
|
|
1907
|
+
*/
|
|
1908
|
+
/**
|
|
1909
|
+
* Listen to DisTubeVoice events and handle the Queue
|
|
1910
|
+
* @private
|
|
1911
|
+
* @param {Queue} queue Queue
|
|
1912
|
+
*/
|
|
1536
1913
|
__privateAdd(this, _voiceEventHandler);
|
|
1914
|
+
/**
|
|
1915
|
+
* Whether or not emit playSong event
|
|
1916
|
+
* @param {Queue} queue Queue
|
|
1917
|
+
* @private
|
|
1918
|
+
* @returns {boolean}
|
|
1919
|
+
*/
|
|
1920
|
+
__privateAdd(this, _emitPlaySong);
|
|
1921
|
+
/**
|
|
1922
|
+
* Handle the queue when a Song finish
|
|
1923
|
+
* @private
|
|
1924
|
+
* @param {Queue} queue queue
|
|
1925
|
+
* @returns {Promise<void>}
|
|
1926
|
+
*/
|
|
1537
1927
|
__privateAdd(this, _handleSongFinish);
|
|
1928
|
+
/**
|
|
1929
|
+
* Handle error while playing
|
|
1930
|
+
* @private
|
|
1931
|
+
* @param {Queue} queue queue
|
|
1932
|
+
* @param {Error} error error
|
|
1933
|
+
*/
|
|
1538
1934
|
__privateAdd(this, _handlePlayingError);
|
|
1539
|
-
__privateAdd(this, _emitPlaySong);
|
|
1540
1935
|
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Collection of {@link Queue}.
|
|
1938
|
+
* @name QueueManager#collection
|
|
1939
|
+
* @type {Discord.Collection<string, Queue>}
|
|
1940
|
+
*/
|
|
1941
|
+
/**
|
|
1942
|
+
* Create a {@link Queue}
|
|
1943
|
+
* @private
|
|
1944
|
+
* @param {Discord.BaseGuildVoiceChannel} channel A voice channel
|
|
1945
|
+
* @param {Song|Song[]} song First song
|
|
1946
|
+
* @param {Discord.BaseGuildTextChannel} textChannel Default text channel
|
|
1947
|
+
* @returns {Promise<Queue|true>} Returns `true` if encounter an error
|
|
1948
|
+
*/
|
|
1541
1949
|
async create(channel, song, textChannel) {
|
|
1542
1950
|
if (this.has(channel.guildId))
|
|
1543
1951
|
throw new DisTubeError("QUEUE_EXIST");
|
|
@@ -1555,49 +1963,46 @@ var QueueManager = class extends GuildIdManager {
|
|
|
1555
1963
|
queue._taskQueue.resolve();
|
|
1556
1964
|
}
|
|
1557
1965
|
}
|
|
1966
|
+
/**
|
|
1967
|
+
* Create a ytdl stream
|
|
1968
|
+
* @param {Queue} queue Queue
|
|
1969
|
+
* @returns {DisTubeStream}
|
|
1970
|
+
*/
|
|
1558
1971
|
createStream(queue) {
|
|
1559
1972
|
const { duration, formats, isLive, source, streamURL } = queue.songs[0];
|
|
1560
|
-
const
|
|
1561
|
-
|
|
1562
|
-
|
|
1973
|
+
const streamOptions = {
|
|
1974
|
+
ffmpegArgs: queue.filters.ffmpegArgs,
|
|
1975
|
+
seek: duration ? queue.beginTime : void 0,
|
|
1976
|
+
isLive,
|
|
1977
|
+
type: this.options.streamType
|
|
1978
|
+
};
|
|
1563
1979
|
if (source === "youtube")
|
|
1564
1980
|
return DisTubeStream.YouTube(formats, streamOptions);
|
|
1565
1981
|
return DisTubeStream.DirectLink(streamURL, streamOptions);
|
|
1566
1982
|
}
|
|
1983
|
+
/**
|
|
1984
|
+
* Play a song on voice connection
|
|
1985
|
+
* @private
|
|
1986
|
+
* @param {Queue} queue The guild queue
|
|
1987
|
+
* @returns {Promise<boolean>} error?
|
|
1988
|
+
*/
|
|
1567
1989
|
async playSong(queue) {
|
|
1568
1990
|
if (!queue)
|
|
1569
1991
|
return true;
|
|
1570
|
-
if (!queue.songs.length) {
|
|
1992
|
+
if (queue.stopped || !queue.songs.length) {
|
|
1571
1993
|
queue.stop();
|
|
1572
1994
|
return true;
|
|
1573
1995
|
}
|
|
1574
|
-
if (queue.stopped)
|
|
1575
|
-
return false;
|
|
1576
1996
|
try {
|
|
1577
1997
|
const song = queue.songs[0];
|
|
1578
|
-
|
|
1579
|
-
if (
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
}
|
|
1583
|
-
} else if (!streamURL) {
|
|
1584
|
-
for (const plugin of [...this.distube.extractorPlugins, ...this.distube.customPlugins]) {
|
|
1585
|
-
if (await plugin.validate(url)) {
|
|
1586
|
-
const info = [plugin.getStreamURL(url), plugin.getRelatedSongs(url)];
|
|
1587
|
-
const result = await Promise.all(info);
|
|
1588
|
-
song.streamURL = result[0];
|
|
1589
|
-
song.related = result[1];
|
|
1590
|
-
break;
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1998
|
+
await this.handler.attachStreamInfo(song);
|
|
1999
|
+
if (queue.stopped || !queue.songs.length) {
|
|
2000
|
+
queue.stop();
|
|
2001
|
+
return true;
|
|
1593
2002
|
}
|
|
1594
2003
|
const stream = this.createStream(queue);
|
|
1595
2004
|
queue.voice.play(stream);
|
|
1596
2005
|
song.streamURL = stream.url;
|
|
1597
|
-
if (queue.stopped)
|
|
1598
|
-
queue.stop();
|
|
1599
|
-
else if (queue.paused)
|
|
1600
|
-
queue.voice.pause();
|
|
1601
2006
|
return false;
|
|
1602
2007
|
} catch (e) {
|
|
1603
2008
|
__privateMethod(this, _handlePlayingError, handlePlayingError_fn).call(this, queue, e);
|
|
@@ -1605,7 +2010,6 @@ var QueueManager = class extends GuildIdManager {
|
|
|
1605
2010
|
}
|
|
1606
2011
|
}
|
|
1607
2012
|
};
|
|
1608
|
-
__name(QueueManager, "QueueManager");
|
|
1609
2013
|
_voiceEventHandler = new WeakSet();
|
|
1610
2014
|
voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
|
|
1611
2015
|
queue._listeners = {
|
|
@@ -1622,6 +2026,10 @@ voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
|
|
|
1622
2026
|
queue.voice.on(event, queue._listeners[event]);
|
|
1623
2027
|
}
|
|
1624
2028
|
}, "#voiceEventHandler");
|
|
2029
|
+
_emitPlaySong = new WeakSet();
|
|
2030
|
+
emitPlaySong_fn = /* @__PURE__ */ __name(function(queue) {
|
|
2031
|
+
return !this.options.emitNewSongOnly || queue.repeatMode === 1 /* SONG */ && queue._next || queue.repeatMode !== 1 /* SONG */ && queue.songs[0]?.id !== queue.songs[1]?.id;
|
|
2032
|
+
}, "#emitPlaySong");
|
|
1625
2033
|
_handleSongFinish = new WeakSet();
|
|
1626
2034
|
handleSongFinish_fn = /* @__PURE__ */ __name(async function(queue) {
|
|
1627
2035
|
this.emit("finishSong", queue, queue.songs[0]);
|
|
@@ -1695,14 +2103,19 @@ Name: ${song.name}`;
|
|
|
1695
2103
|
queue.stop();
|
|
1696
2104
|
}
|
|
1697
2105
|
}, "#handlePlayingError");
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
return !this.options.emitNewSongOnly || queue.repeatMode === 1 /* SONG */ && queue._next || queue.repeatMode !== 1 /* SONG */ && queue.songs[0]?.id !== queue.songs[1]?.id;
|
|
1701
|
-
}, "#emitPlaySong");
|
|
2106
|
+
__name(_QueueManager, "QueueManager");
|
|
2107
|
+
var QueueManager = _QueueManager;
|
|
1702
2108
|
|
|
1703
2109
|
// src/struct/Queue.ts
|
|
1704
2110
|
var _filters;
|
|
1705
|
-
var
|
|
2111
|
+
var _Queue = class _Queue extends DisTubeBase {
|
|
2112
|
+
/**
|
|
2113
|
+
* Create a queue for the guild
|
|
2114
|
+
* @param {DisTube} distube DisTube
|
|
2115
|
+
* @param {DisTubeVoice} voice Voice connection
|
|
2116
|
+
* @param {Song|Song[]} song First song(s)
|
|
2117
|
+
* @param {Discord.BaseGuildTextChannel?} textChannel Default text channel
|
|
2118
|
+
*/
|
|
1706
2119
|
constructor(distube, voice, song, textChannel) {
|
|
1707
2120
|
super(distube);
|
|
1708
2121
|
__publicField(this, "id");
|
|
@@ -1741,24 +2154,58 @@ var Queue = class extends DisTubeBase {
|
|
|
1741
2154
|
this._taskQueue = new TaskQueue();
|
|
1742
2155
|
this._listeners = void 0;
|
|
1743
2156
|
}
|
|
2157
|
+
/**
|
|
2158
|
+
* The client user as a `GuildMember` of this queue's guild
|
|
2159
|
+
* @type {Discord.GuildMember?}
|
|
2160
|
+
*/
|
|
1744
2161
|
get clientMember() {
|
|
1745
2162
|
return this.voice.channel.guild.members.me ?? void 0;
|
|
1746
2163
|
}
|
|
2164
|
+
/**
|
|
2165
|
+
* The filter manager of the queue
|
|
2166
|
+
* @type {FilterManager}
|
|
2167
|
+
* @readonly
|
|
2168
|
+
*/
|
|
1747
2169
|
get filters() {
|
|
1748
2170
|
return __privateGet(this, _filters);
|
|
1749
2171
|
}
|
|
2172
|
+
/**
|
|
2173
|
+
* Formatted duration string.
|
|
2174
|
+
* @type {string}
|
|
2175
|
+
* @readonly
|
|
2176
|
+
*/
|
|
1750
2177
|
get formattedDuration() {
|
|
1751
2178
|
return formatDuration(this.duration);
|
|
1752
2179
|
}
|
|
2180
|
+
/**
|
|
2181
|
+
* Queue's duration.
|
|
2182
|
+
* @type {number}
|
|
2183
|
+
* @readonly
|
|
2184
|
+
*/
|
|
1753
2185
|
get duration() {
|
|
1754
2186
|
return this.songs.length ? this.songs.reduce((prev, next) => prev + next.duration, 0) : 0;
|
|
1755
2187
|
}
|
|
2188
|
+
/**
|
|
2189
|
+
* What time in the song is playing (in seconds).
|
|
2190
|
+
* @type {number}
|
|
2191
|
+
* @readonly
|
|
2192
|
+
*/
|
|
1756
2193
|
get currentTime() {
|
|
1757
2194
|
return this.voice.playbackDuration + this.beginTime;
|
|
1758
2195
|
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Formatted {@link Queue#currentTime} string.
|
|
2198
|
+
* @type {string}
|
|
2199
|
+
* @readonly
|
|
2200
|
+
*/
|
|
1759
2201
|
get formattedCurrentTime() {
|
|
1760
2202
|
return formatDuration(this.currentTime);
|
|
1761
2203
|
}
|
|
2204
|
+
/**
|
|
2205
|
+
* The voice channel playing in.
|
|
2206
|
+
* @type {Discord.VoiceChannel|Discord.StageChannel|null}
|
|
2207
|
+
* @readonly
|
|
2208
|
+
*/
|
|
1762
2209
|
get voiceChannel() {
|
|
1763
2210
|
return this.clientMember?.voice?.channel ?? null;
|
|
1764
2211
|
}
|
|
@@ -1768,6 +2215,14 @@ var Queue = class extends DisTubeBase {
|
|
|
1768
2215
|
set volume(value) {
|
|
1769
2216
|
this.voice.volume = value;
|
|
1770
2217
|
}
|
|
2218
|
+
/**
|
|
2219
|
+
* @private
|
|
2220
|
+
* Add a Song or an array of Song to the queue
|
|
2221
|
+
* @param {Song|Song[]} song Song to add
|
|
2222
|
+
* @param {number} [position=0] Position to add, <= 0 to add to the end of the queue
|
|
2223
|
+
* @throws {Error}
|
|
2224
|
+
* @returns {Queue} The guild queue
|
|
2225
|
+
*/
|
|
1771
2226
|
addToQueue(song, position = 0) {
|
|
1772
2227
|
if (!song || Array.isArray(song) && !song.length) {
|
|
1773
2228
|
throw new DisTubeError("INVALID_TYPE", ["Song", "Array<Song>"], song, "song");
|
|
@@ -1791,6 +2246,10 @@ var Queue = class extends DisTubeBase {
|
|
|
1791
2246
|
delete song.formats;
|
|
1792
2247
|
return this;
|
|
1793
2248
|
}
|
|
2249
|
+
/**
|
|
2250
|
+
* Pause the guild stream
|
|
2251
|
+
* @returns {Queue} The guild queue
|
|
2252
|
+
*/
|
|
1794
2253
|
pause() {
|
|
1795
2254
|
if (this.paused)
|
|
1796
2255
|
throw new DisTubeError("PAUSED");
|
|
@@ -1799,6 +2258,10 @@ var Queue = class extends DisTubeBase {
|
|
|
1799
2258
|
this.voice.pause();
|
|
1800
2259
|
return this;
|
|
1801
2260
|
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Resume the guild stream
|
|
2263
|
+
* @returns {Queue} The guild queue
|
|
2264
|
+
*/
|
|
1802
2265
|
resume() {
|
|
1803
2266
|
if (this.playing)
|
|
1804
2267
|
throw new DisTubeError("RESUMED");
|
|
@@ -1807,10 +2270,22 @@ var Queue = class extends DisTubeBase {
|
|
|
1807
2270
|
this.voice.unpause();
|
|
1808
2271
|
return this;
|
|
1809
2272
|
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Set the guild stream's volume
|
|
2275
|
+
* @param {number} percent The percentage of volume you want to set
|
|
2276
|
+
* @returns {Queue} The guild queue
|
|
2277
|
+
*/
|
|
1810
2278
|
setVolume(percent) {
|
|
1811
2279
|
this.volume = percent;
|
|
1812
2280
|
return this;
|
|
1813
2281
|
}
|
|
2282
|
+
/**
|
|
2283
|
+
* Skip the playing song if there is a next song in the queue.
|
|
2284
|
+
* <info>If {@link Queue#autoplay} is `true` and there is no up next song,
|
|
2285
|
+
* DisTube will add and play a related song.</info>
|
|
2286
|
+
* @returns {Promise<Song>} The song will skip to
|
|
2287
|
+
* @throws {Error}
|
|
2288
|
+
*/
|
|
1814
2289
|
async skip() {
|
|
1815
2290
|
await this._taskQueue.queuing();
|
|
1816
2291
|
try {
|
|
@@ -1828,6 +2303,11 @@ var Queue = class extends DisTubeBase {
|
|
|
1828
2303
|
this._taskQueue.resolve();
|
|
1829
2304
|
}
|
|
1830
2305
|
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Play the previous song if exists
|
|
2308
|
+
* @returns {Promise<Song>} The guild queue
|
|
2309
|
+
* @throws {Error}
|
|
2310
|
+
*/
|
|
1831
2311
|
async previous() {
|
|
1832
2312
|
await this._taskQueue.queuing();
|
|
1833
2313
|
try {
|
|
@@ -1844,6 +2324,10 @@ var Queue = class extends DisTubeBase {
|
|
|
1844
2324
|
this._taskQueue.resolve();
|
|
1845
2325
|
}
|
|
1846
2326
|
}
|
|
2327
|
+
/**
|
|
2328
|
+
* Shuffle the queue's songs
|
|
2329
|
+
* @returns {Promise<Queue>} The guild queue
|
|
2330
|
+
*/
|
|
1847
2331
|
async shuffle() {
|
|
1848
2332
|
await this._taskQueue.queuing();
|
|
1849
2333
|
try {
|
|
@@ -1860,6 +2344,14 @@ var Queue = class extends DisTubeBase {
|
|
|
1860
2344
|
this._taskQueue.resolve();
|
|
1861
2345
|
}
|
|
1862
2346
|
}
|
|
2347
|
+
/**
|
|
2348
|
+
* Jump to the song position in the queue.
|
|
2349
|
+
* The next one is 1, 2,...
|
|
2350
|
+
* The previous one is -1, -2,...
|
|
2351
|
+
* @param {number} position The song position to play
|
|
2352
|
+
* @returns {Promise<Song>} The new Song will be played
|
|
2353
|
+
* @throws {Error} if `num` is invalid number
|
|
2354
|
+
*/
|
|
1863
2355
|
async jump(position) {
|
|
1864
2356
|
await this._taskQueue.queuing();
|
|
1865
2357
|
try {
|
|
@@ -1893,6 +2385,12 @@ var Queue = class extends DisTubeBase {
|
|
|
1893
2385
|
this._taskQueue.resolve();
|
|
1894
2386
|
}
|
|
1895
2387
|
}
|
|
2388
|
+
/**
|
|
2389
|
+
* Set the repeat mode of the guild queue.\
|
|
2390
|
+
* Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
|
|
2391
|
+
* @param {RepeatMode?} [mode] The repeat modes (toggle if `undefined`)
|
|
2392
|
+
* @returns {RepeatMode} The new repeat mode
|
|
2393
|
+
*/
|
|
1896
2394
|
setRepeatMode(mode) {
|
|
1897
2395
|
if (mode !== void 0 && !Object.values(RepeatMode).includes(mode)) {
|
|
1898
2396
|
throw new DisTubeError("INVALID_TYPE", ["RepeatMode", "undefined"], mode, "mode");
|
|
@@ -1905,6 +2403,11 @@ var Queue = class extends DisTubeBase {
|
|
|
1905
2403
|
this.repeatMode = mode;
|
|
1906
2404
|
return this.repeatMode;
|
|
1907
2405
|
}
|
|
2406
|
+
/**
|
|
2407
|
+
* Set the playing time to another position
|
|
2408
|
+
* @param {number} time Time in seconds
|
|
2409
|
+
* @returns {Queue} The guild queue
|
|
2410
|
+
*/
|
|
1908
2411
|
seek(time) {
|
|
1909
2412
|
if (typeof time !== "number")
|
|
1910
2413
|
throw new DisTubeError("INVALID_TYPE", "number", time, "time");
|
|
@@ -1914,6 +2417,11 @@ var Queue = class extends DisTubeBase {
|
|
|
1914
2417
|
this.queues.playSong(this);
|
|
1915
2418
|
return this;
|
|
1916
2419
|
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Add a related song of the playing song to the queue
|
|
2422
|
+
* @returns {Promise<Song>} The added song
|
|
2423
|
+
* @throws {Error}
|
|
2424
|
+
*/
|
|
1917
2425
|
async addRelatedSong() {
|
|
1918
2426
|
if (!this.songs?.[0])
|
|
1919
2427
|
throw new DisTubeError("NO_PLAYING");
|
|
@@ -1926,6 +2434,9 @@ var Queue = class extends DisTubeBase {
|
|
|
1926
2434
|
this.addToQueue(song);
|
|
1927
2435
|
return song;
|
|
1928
2436
|
}
|
|
2437
|
+
/**
|
|
2438
|
+
* Stop the guild stream and delete the queue
|
|
2439
|
+
*/
|
|
1929
2440
|
async stop() {
|
|
1930
2441
|
await this._taskQueue.queuing();
|
|
1931
2442
|
try {
|
|
@@ -1941,6 +2452,11 @@ var Queue = class extends DisTubeBase {
|
|
|
1941
2452
|
this._taskQueue.resolve();
|
|
1942
2453
|
}
|
|
1943
2454
|
}
|
|
2455
|
+
/**
|
|
2456
|
+
* Remove the queue from the manager
|
|
2457
|
+
* (This does not leave the voice channel even if {@link DisTubeOptions|DisTubeOptions.leaveOnStop} is enabled)
|
|
2458
|
+
* @private
|
|
2459
|
+
*/
|
|
1944
2460
|
remove() {
|
|
1945
2461
|
this.stopped = true;
|
|
1946
2462
|
this.songs = [];
|
|
@@ -1953,72 +2469,138 @@ var Queue = class extends DisTubeBase {
|
|
|
1953
2469
|
this.queues.remove(this.id);
|
|
1954
2470
|
this.emit("deleteQueue", this);
|
|
1955
2471
|
}
|
|
2472
|
+
/**
|
|
2473
|
+
* Toggle autoplay mode
|
|
2474
|
+
* @returns {boolean} Autoplay mode state
|
|
2475
|
+
*/
|
|
1956
2476
|
toggleAutoplay() {
|
|
1957
2477
|
this.autoplay = !this.autoplay;
|
|
1958
2478
|
return this.autoplay;
|
|
1959
2479
|
}
|
|
1960
2480
|
};
|
|
1961
|
-
__name(Queue, "Queue");
|
|
1962
2481
|
_filters = new WeakMap();
|
|
2482
|
+
__name(_Queue, "Queue");
|
|
2483
|
+
var Queue = _Queue;
|
|
1963
2484
|
|
|
1964
2485
|
// src/struct/Plugin.ts
|
|
1965
|
-
var
|
|
2486
|
+
var _Plugin = class _Plugin {
|
|
1966
2487
|
constructor() {
|
|
1967
2488
|
__publicField(this, "distube");
|
|
1968
2489
|
}
|
|
1969
2490
|
init(distube) {
|
|
1970
2491
|
this.distube = distube;
|
|
1971
2492
|
}
|
|
2493
|
+
/**
|
|
2494
|
+
* Type of the plugin
|
|
2495
|
+
* @name Plugin#type
|
|
2496
|
+
* @type {PluginType}
|
|
2497
|
+
*/
|
|
2498
|
+
/**
|
|
2499
|
+
* Emit an event to the {@link DisTube} class
|
|
2500
|
+
* @param {string} eventName Event name
|
|
2501
|
+
* @param {...any} args arguments
|
|
2502
|
+
* @returns {boolean}
|
|
2503
|
+
*/
|
|
1972
2504
|
emit(eventName, ...args) {
|
|
1973
2505
|
return this.distube.emit(eventName, ...args);
|
|
1974
2506
|
}
|
|
2507
|
+
/**
|
|
2508
|
+
* Emit error event to the {@link DisTube} class
|
|
2509
|
+
* @param {Error} error error
|
|
2510
|
+
* @param {Discord.BaseGuildTextChannel} [channel] Text channel where the error is encountered.
|
|
2511
|
+
*/
|
|
1975
2512
|
emitError(error, channel) {
|
|
1976
2513
|
this.distube.emitError(error, channel);
|
|
1977
2514
|
}
|
|
2515
|
+
/**
|
|
2516
|
+
* The queue manager
|
|
2517
|
+
* @type {QueueManager}
|
|
2518
|
+
* @readonly
|
|
2519
|
+
*/
|
|
1978
2520
|
get queues() {
|
|
1979
2521
|
return this.distube.queues;
|
|
1980
2522
|
}
|
|
2523
|
+
/**
|
|
2524
|
+
* The voice manager
|
|
2525
|
+
* @type {DisTubeVoiceManager}
|
|
2526
|
+
* @readonly
|
|
2527
|
+
*/
|
|
1981
2528
|
get voices() {
|
|
1982
2529
|
return this.distube.voices;
|
|
1983
2530
|
}
|
|
2531
|
+
/**
|
|
2532
|
+
* Discord.js client
|
|
2533
|
+
* @type {Discord.Client}
|
|
2534
|
+
* @readonly
|
|
2535
|
+
*/
|
|
1984
2536
|
get client() {
|
|
1985
2537
|
return this.distube.client;
|
|
1986
2538
|
}
|
|
2539
|
+
/**
|
|
2540
|
+
* DisTube options
|
|
2541
|
+
* @type {DisTubeOptions}
|
|
2542
|
+
* @readonly
|
|
2543
|
+
*/
|
|
1987
2544
|
get options() {
|
|
1988
2545
|
return this.distube.options;
|
|
1989
2546
|
}
|
|
2547
|
+
/**
|
|
2548
|
+
* DisTube handler
|
|
2549
|
+
* @type {DisTubeHandler}
|
|
2550
|
+
* @readonly
|
|
2551
|
+
*/
|
|
1990
2552
|
get handler() {
|
|
1991
2553
|
return this.distube.handler;
|
|
1992
2554
|
}
|
|
2555
|
+
/**
|
|
2556
|
+
* Check if the string is working with this plugin
|
|
2557
|
+
* @param {string} _string Input string
|
|
2558
|
+
* @returns {boolean|Promise<boolean>}
|
|
2559
|
+
*/
|
|
1993
2560
|
validate(_string) {
|
|
1994
2561
|
return false;
|
|
1995
2562
|
}
|
|
2563
|
+
/**
|
|
2564
|
+
* Get the stream url from {@link Song#url}. Returns {@link Song#url} by default.
|
|
2565
|
+
* Not needed if the plugin plays song from YouTube.
|
|
2566
|
+
* @param {string} url Input url
|
|
2567
|
+
* @returns {string|Promise<string>}
|
|
2568
|
+
*/
|
|
1996
2569
|
getStreamURL(url) {
|
|
1997
2570
|
return url;
|
|
1998
2571
|
}
|
|
2572
|
+
/**
|
|
2573
|
+
* Get related songs from a supported url. {@link Song#member} should be `undefined`.
|
|
2574
|
+
* Not needed to add {@link Song#related} because it will be added with this function later.
|
|
2575
|
+
* @param {string} _url Input url
|
|
2576
|
+
* @returns {Song[]|Promise<Song[]>}
|
|
2577
|
+
*/
|
|
1999
2578
|
getRelatedSongs(_url) {
|
|
2000
2579
|
return [];
|
|
2001
2580
|
}
|
|
2002
2581
|
};
|
|
2003
|
-
__name(
|
|
2582
|
+
__name(_Plugin, "Plugin");
|
|
2583
|
+
var Plugin = _Plugin;
|
|
2004
2584
|
|
|
2005
2585
|
// src/struct/CustomPlugin.ts
|
|
2006
|
-
var
|
|
2586
|
+
var _CustomPlugin = class _CustomPlugin extends Plugin {
|
|
2007
2587
|
constructor() {
|
|
2008
2588
|
super(...arguments);
|
|
2009
2589
|
__publicField(this, "type", "custom" /* CUSTOM */);
|
|
2010
2590
|
}
|
|
2011
2591
|
};
|
|
2012
|
-
__name(
|
|
2592
|
+
__name(_CustomPlugin, "CustomPlugin");
|
|
2593
|
+
var CustomPlugin = _CustomPlugin;
|
|
2013
2594
|
|
|
2014
2595
|
// src/struct/ExtractorPlugin.ts
|
|
2015
|
-
var
|
|
2596
|
+
var _ExtractorPlugin = class _ExtractorPlugin extends Plugin {
|
|
2016
2597
|
constructor() {
|
|
2017
2598
|
super(...arguments);
|
|
2018
2599
|
__publicField(this, "type", "extractor" /* EXTRACTOR */);
|
|
2019
2600
|
}
|
|
2020
2601
|
};
|
|
2021
|
-
__name(
|
|
2602
|
+
__name(_ExtractorPlugin, "ExtractorPlugin");
|
|
2603
|
+
var ExtractorPlugin = _ExtractorPlugin;
|
|
2022
2604
|
|
|
2023
2605
|
// src/util.ts
|
|
2024
2606
|
var import_url = require("url");
|
|
@@ -2027,7 +2609,7 @@ var formatInt = /* @__PURE__ */ __name((int) => int < 10 ? `0${int}` : int, "for
|
|
|
2027
2609
|
function formatDuration(sec) {
|
|
2028
2610
|
if (!sec || !Number(sec))
|
|
2029
2611
|
return "00:00";
|
|
2030
|
-
const seconds = Math.
|
|
2612
|
+
const seconds = Math.floor(sec % 60);
|
|
2031
2613
|
const minutes = Math.floor(sec % 3600 / 60);
|
|
2032
2614
|
const hours = Math.floor(sec / 3600);
|
|
2033
2615
|
if (hours > 0)
|
|
@@ -2062,12 +2644,13 @@ function parseNumber(input) {
|
|
|
2062
2644
|
return Number(input) || 0;
|
|
2063
2645
|
}
|
|
2064
2646
|
__name(parseNumber, "parseNumber");
|
|
2647
|
+
var SUPPORTED_PROTOCOL = ["https:", "http:", "file:"];
|
|
2065
2648
|
function isURL(input) {
|
|
2066
2649
|
if (typeof input !== "string" || input.includes(" "))
|
|
2067
2650
|
return false;
|
|
2068
2651
|
try {
|
|
2069
2652
|
const url = new import_url.URL(input);
|
|
2070
|
-
if (!
|
|
2653
|
+
if (!SUPPORTED_PROTOCOL.some((p) => p === url.protocol))
|
|
2071
2654
|
return false;
|
|
2072
2655
|
} catch {
|
|
2073
2656
|
return false;
|
|
@@ -2106,7 +2689,7 @@ function isMemberInstance(member) {
|
|
|
2106
2689
|
}
|
|
2107
2690
|
__name(isMemberInstance, "isMemberInstance");
|
|
2108
2691
|
function isTextChannelInstance(channel) {
|
|
2109
|
-
return !!channel && isSnowflake(channel.id) && isSnowflake(channel.guildId) && typeof channel.name === "string" && import_discord3.Constants.TextBasedChannelTypes.includes(channel.type) &&
|
|
2692
|
+
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";
|
|
2110
2693
|
}
|
|
2111
2694
|
__name(isTextChannelInstance, "isTextChannelInstance");
|
|
2112
2695
|
function isMessageInstance(message) {
|
|
@@ -2166,20 +2749,30 @@ function objectKeys(obj) {
|
|
|
2166
2749
|
return Object.keys(obj);
|
|
2167
2750
|
}
|
|
2168
2751
|
__name(objectKeys, "objectKeys");
|
|
2752
|
+
function isNsfwChannel(channel) {
|
|
2753
|
+
if (!isTextChannelInstance(channel))
|
|
2754
|
+
return false;
|
|
2755
|
+
if (channel.isThread())
|
|
2756
|
+
return channel.parent?.nsfw ?? false;
|
|
2757
|
+
return channel.nsfw;
|
|
2758
|
+
}
|
|
2759
|
+
__name(isNsfwChannel, "isNsfwChannel");
|
|
2169
2760
|
|
|
2170
2761
|
// src/plugin/DirectLink.ts
|
|
2171
2762
|
var import_undici = require("undici");
|
|
2172
|
-
var
|
|
2763
|
+
var _DirectLinkPlugin = class _DirectLinkPlugin extends ExtractorPlugin {
|
|
2173
2764
|
async validate(url) {
|
|
2174
2765
|
try {
|
|
2175
2766
|
const headers = await (0, import_undici.request)(url, { method: "HEAD" }).then((res) => res.headers);
|
|
2176
|
-
const
|
|
2177
|
-
|
|
2767
|
+
const types = headers["content-type"];
|
|
2768
|
+
const type = Array.isArray(types) ? types[0] : types;
|
|
2769
|
+
if (["audio/", "video/", "application/ogg"].some((s) => type?.startsWith(s)))
|
|
2178
2770
|
return true;
|
|
2179
2771
|
} catch {
|
|
2180
2772
|
}
|
|
2181
2773
|
return false;
|
|
2182
2774
|
}
|
|
2775
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2183
2776
|
async resolve(url, options = {}) {
|
|
2184
2777
|
url = url.replace(/\/+$/, "");
|
|
2185
2778
|
return new Song(
|
|
@@ -2192,13 +2785,28 @@ var DirectLinkPlugin = class extends ExtractorPlugin {
|
|
|
2192
2785
|
);
|
|
2193
2786
|
}
|
|
2194
2787
|
};
|
|
2195
|
-
__name(
|
|
2788
|
+
__name(_DirectLinkPlugin, "DirectLinkPlugin");
|
|
2789
|
+
var DirectLinkPlugin = _DirectLinkPlugin;
|
|
2196
2790
|
|
|
2197
2791
|
// src/DisTube.ts
|
|
2198
2792
|
var import_ytsr = __toESM(require("@distube/ytsr"));
|
|
2199
2793
|
var import_tiny_typed_emitter2 = require("tiny-typed-emitter");
|
|
2200
2794
|
var { version } = require_package();
|
|
2201
|
-
var
|
|
2795
|
+
var _DisTube = class _DisTube extends import_tiny_typed_emitter2.TypedEmitter {
|
|
2796
|
+
/**
|
|
2797
|
+
* Create a new DisTube class.
|
|
2798
|
+
* @param {Discord.Client} client Discord.JS client
|
|
2799
|
+
* @param {DisTubeOptions} [otp] Custom DisTube options
|
|
2800
|
+
* @throws {DisTubeError}
|
|
2801
|
+
* @example
|
|
2802
|
+
* const Discord = require('discord.js'),
|
|
2803
|
+
* DisTube = require('distube'),
|
|
2804
|
+
* client = new Discord.Client();
|
|
2805
|
+
* // Create a new DisTube
|
|
2806
|
+
* const distube = new DisTube.default(client, { searchSongs: 10 });
|
|
2807
|
+
* // client.DisTube = distube // make it access easily
|
|
2808
|
+
* client.login("Your Discord Bot Token")
|
|
2809
|
+
*/
|
|
2202
2810
|
constructor(client, otp = {}) {
|
|
2203
2811
|
super();
|
|
2204
2812
|
__publicField(this, "handler");
|
|
@@ -2228,9 +2836,36 @@ var DisTube = class extends import_tiny_typed_emitter2.TypedEmitter {
|
|
|
2228
2836
|
static get version() {
|
|
2229
2837
|
return version;
|
|
2230
2838
|
}
|
|
2839
|
+
/**
|
|
2840
|
+
* DisTube version
|
|
2841
|
+
* @type {string}
|
|
2842
|
+
*/
|
|
2231
2843
|
get version() {
|
|
2232
2844
|
return version;
|
|
2233
2845
|
}
|
|
2846
|
+
/**
|
|
2847
|
+
* Play / add a song or playlist from url. Search and play a song if it is not a valid url.
|
|
2848
|
+
*
|
|
2849
|
+
* @param {Discord.BaseGuildVoiceChannel} voiceChannel The channel will be joined if the bot isn't in any channels,
|
|
2850
|
+
* the bot will be moved to this channel if {@link DisTubeOptions}.joinNewVoiceChannel is `true`
|
|
2851
|
+
* @param {string|Song|SearchResult|Playlist} song URL | Search string |
|
|
2852
|
+
* {@link Song} | {@link SearchResult} | {@link Playlist}
|
|
2853
|
+
* @param {PlayOptions} [options] Optional options
|
|
2854
|
+
* @throws {DisTubeError}
|
|
2855
|
+
* @example
|
|
2856
|
+
* client.on('message', (message) => {
|
|
2857
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
2858
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
2859
|
+
* const command = args.shift();
|
|
2860
|
+
* if (command == "play")
|
|
2861
|
+
* distube.play(message.member.voice.channel, args.join(" "), {
|
|
2862
|
+
* member: message.member,
|
|
2863
|
+
* textChannel: message.channel,
|
|
2864
|
+
* message
|
|
2865
|
+
* });
|
|
2866
|
+
* });
|
|
2867
|
+
* @returns {Promise<void>}
|
|
2868
|
+
*/
|
|
2234
2869
|
async play(voiceChannel, song, options = {}) {
|
|
2235
2870
|
if (!isSupportedVoiceChannel(voiceChannel)) {
|
|
2236
2871
|
throw new DisTubeError("INVALID_TYPE", "BaseGuildVoiceChannel", voiceChannel, "voiceChannel");
|
|
@@ -2297,6 +2932,20 @@ ${e.message}`;
|
|
|
2297
2932
|
queue?._taskQueue.resolve();
|
|
2298
2933
|
}
|
|
2299
2934
|
}
|
|
2935
|
+
/**
|
|
2936
|
+
* Create a custom playlist
|
|
2937
|
+
* @returns {Promise<Playlist>}
|
|
2938
|
+
* @param {Array<string|Song|SearchResult>} songs Array of url, Song or SearchResult
|
|
2939
|
+
* @param {CustomPlaylistOptions} [options] Optional options
|
|
2940
|
+
* @example
|
|
2941
|
+
* const songs = ["https://www.youtube.com/watch?v=xxx", "https://www.youtube.com/watch?v=yyy"];
|
|
2942
|
+
* const playlist = await distube.createCustomPlaylist(songs, {
|
|
2943
|
+
* member: message.member,
|
|
2944
|
+
* properties: { name: "My playlist name", source: "custom" },
|
|
2945
|
+
* parallel: true
|
|
2946
|
+
* });
|
|
2947
|
+
* distube.play(voiceChannel, playlist, { ... });
|
|
2948
|
+
*/
|
|
2300
2949
|
async createCustomPlaylist(songs, options = {}) {
|
|
2301
2950
|
const { member, properties, parallel, metadata } = { parallel: true, ...options };
|
|
2302
2951
|
if (!Array.isArray(songs))
|
|
@@ -2311,8 +2960,6 @@ ${e.message}`;
|
|
|
2311
2960
|
if (member && !isMemberInstance(member)) {
|
|
2312
2961
|
throw new DisTubeError("INVALID_TYPE", "Discord.Member", member, "options.member");
|
|
2313
2962
|
}
|
|
2314
|
-
if (!filteredSongs.length)
|
|
2315
|
-
throw new DisTubeError("NO_VALID_SONG");
|
|
2316
2963
|
let resolvedSongs;
|
|
2317
2964
|
if (parallel) {
|
|
2318
2965
|
const promises = filteredSongs.map(
|
|
@@ -2328,6 +2975,18 @@ ${e.message}`;
|
|
|
2328
2975
|
}
|
|
2329
2976
|
return new Playlist(resolvedSongs, { member, properties, metadata });
|
|
2330
2977
|
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Search for a song. You can customize how user answers instead of send a number.
|
|
2980
|
+
* Then use {@link DisTube#play} to play it.
|
|
2981
|
+
*
|
|
2982
|
+
* @param {string} string The string search for
|
|
2983
|
+
* @param {Object} options Search options
|
|
2984
|
+
* @param {number} [options.limit=10] Limit the results
|
|
2985
|
+
* @param {SearchResultType} [options.type=SearchResultType.VIDEO] Type of results (`video` or `playlist`).
|
|
2986
|
+
* @param {boolean} [options.safeSearch=false] Whether or not use safe search (YouTube restricted mode)
|
|
2987
|
+
* @throws {Error}
|
|
2988
|
+
* @returns {Promise<Array<SearchResult>>} Array of results
|
|
2989
|
+
*/
|
|
2331
2990
|
async search(string, options = {}) {
|
|
2332
2991
|
const opts = { type: "video" /* VIDEO */, limit: 10, safeSearch: false, ...options };
|
|
2333
2992
|
if (typeof opts.type !== "string" || !["video", "playlist"].includes(opts.type)) {
|
|
@@ -2357,63 +3016,234 @@ ${e.message}`;
|
|
|
2357
3016
|
return this.search(string, options);
|
|
2358
3017
|
}
|
|
2359
3018
|
}
|
|
3019
|
+
/**
|
|
3020
|
+
* Get the guild queue
|
|
3021
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3022
|
+
* @returns {Queue?}
|
|
3023
|
+
* @throws {Error}
|
|
3024
|
+
* @example
|
|
3025
|
+
* client.on('message', (message) => {
|
|
3026
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3027
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3028
|
+
* const command = args.shift();
|
|
3029
|
+
* if (command == "queue") {
|
|
3030
|
+
* const queue = distube.getQueue(message);
|
|
3031
|
+
* message.channel.send('Current queue:\n' + queue.songs.map((song, id) =>
|
|
3032
|
+
* `**${id+1}**. [${song.name}](${song.url}) - \`${song.formattedDuration}\``
|
|
3033
|
+
* ).join("\n"));
|
|
3034
|
+
* }
|
|
3035
|
+
* });
|
|
3036
|
+
*/
|
|
2360
3037
|
getQueue(guild) {
|
|
2361
3038
|
return this.queues.get(guild);
|
|
2362
3039
|
}
|
|
3040
|
+
/**
|
|
3041
|
+
* Pause the guild stream
|
|
3042
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3043
|
+
* @returns {Queue} The guild queue
|
|
3044
|
+
* @throws {Error}
|
|
3045
|
+
*/
|
|
2363
3046
|
pause(guild) {
|
|
2364
3047
|
const q = this.getQueue(guild);
|
|
2365
3048
|
if (!q)
|
|
2366
3049
|
throw new DisTubeError("NO_QUEUE");
|
|
2367
3050
|
return q.pause();
|
|
2368
3051
|
}
|
|
3052
|
+
/**
|
|
3053
|
+
* Resume the guild stream
|
|
3054
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3055
|
+
* @returns {Queue} The guild queue
|
|
3056
|
+
* @throws {Error}
|
|
3057
|
+
*/
|
|
2369
3058
|
resume(guild) {
|
|
2370
3059
|
const q = this.getQueue(guild);
|
|
2371
3060
|
if (!q)
|
|
2372
3061
|
throw new DisTubeError("NO_QUEUE");
|
|
2373
3062
|
return q.resume();
|
|
2374
3063
|
}
|
|
3064
|
+
/**
|
|
3065
|
+
* Stop the guild stream
|
|
3066
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3067
|
+
* @returns {Promise<void>}
|
|
3068
|
+
* @throws {Error}
|
|
3069
|
+
* @example
|
|
3070
|
+
* client.on('message', (message) => {
|
|
3071
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3072
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3073
|
+
* const command = args.shift();
|
|
3074
|
+
* if (command == "stop") {
|
|
3075
|
+
* distube.stop(message);
|
|
3076
|
+
* message.channel.send("Stopped the queue!");
|
|
3077
|
+
* }
|
|
3078
|
+
* });
|
|
3079
|
+
*/
|
|
2375
3080
|
stop(guild) {
|
|
2376
3081
|
const q = this.getQueue(guild);
|
|
2377
3082
|
if (!q)
|
|
2378
3083
|
throw new DisTubeError("NO_QUEUE");
|
|
2379
3084
|
return q.stop();
|
|
2380
3085
|
}
|
|
3086
|
+
/**
|
|
3087
|
+
* Set the guild stream's volume
|
|
3088
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3089
|
+
* @param {number} percent The percentage of volume you want to set
|
|
3090
|
+
* @returns {Queue} The guild queue
|
|
3091
|
+
* @throws {Error}
|
|
3092
|
+
* @example
|
|
3093
|
+
* client.on('message', (message) => {
|
|
3094
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3095
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3096
|
+
* const command = args.shift();
|
|
3097
|
+
* if (command == "volume")
|
|
3098
|
+
* distube.setVolume(message, Number(args[0]));
|
|
3099
|
+
* });
|
|
3100
|
+
*/
|
|
2381
3101
|
setVolume(guild, percent) {
|
|
2382
3102
|
const q = this.getQueue(guild);
|
|
2383
3103
|
if (!q)
|
|
2384
3104
|
throw new DisTubeError("NO_QUEUE");
|
|
2385
3105
|
return q.setVolume(percent);
|
|
2386
3106
|
}
|
|
3107
|
+
/**
|
|
3108
|
+
* Skip the playing song if there is a next song in the queue.
|
|
3109
|
+
* <info>If {@link Queue#autoplay} is `true` and there is no up next song,
|
|
3110
|
+
* DisTube will add and play a related song.</info>
|
|
3111
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3112
|
+
* @returns {Promise<Song>} The new Song will be played
|
|
3113
|
+
* @throws {Error}
|
|
3114
|
+
* @example
|
|
3115
|
+
* client.on('message', (message) => {
|
|
3116
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3117
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3118
|
+
* const command = args.shift();
|
|
3119
|
+
* if (command == "skip")
|
|
3120
|
+
* distube.skip(message);
|
|
3121
|
+
* });
|
|
3122
|
+
*/
|
|
2387
3123
|
skip(guild) {
|
|
2388
3124
|
const q = this.getQueue(guild);
|
|
2389
3125
|
if (!q)
|
|
2390
3126
|
throw new DisTubeError("NO_QUEUE");
|
|
2391
3127
|
return q.skip();
|
|
2392
3128
|
}
|
|
3129
|
+
/**
|
|
3130
|
+
* Play the previous song
|
|
3131
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3132
|
+
* @returns {Promise<Song>} The new Song will be played
|
|
3133
|
+
* @throws {Error}
|
|
3134
|
+
* @example
|
|
3135
|
+
* client.on('message', (message) => {
|
|
3136
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3137
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3138
|
+
* const command = args.shift();
|
|
3139
|
+
* if (command == "previous")
|
|
3140
|
+
* distube.previous(message);
|
|
3141
|
+
* });
|
|
3142
|
+
*/
|
|
2393
3143
|
previous(guild) {
|
|
2394
3144
|
const q = this.getQueue(guild);
|
|
2395
3145
|
if (!q)
|
|
2396
3146
|
throw new DisTubeError("NO_QUEUE");
|
|
2397
3147
|
return q.previous();
|
|
2398
3148
|
}
|
|
3149
|
+
/**
|
|
3150
|
+
* Shuffle the guild queue songs
|
|
3151
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3152
|
+
* @returns {Promise<Queue>} The guild queue
|
|
3153
|
+
* @example
|
|
3154
|
+
* client.on('message', (message) => {
|
|
3155
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3156
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3157
|
+
* const command = args.shift();
|
|
3158
|
+
* if (command == "shuffle")
|
|
3159
|
+
* distube.shuffle(message);
|
|
3160
|
+
* });
|
|
3161
|
+
*/
|
|
2399
3162
|
shuffle(guild) {
|
|
2400
3163
|
const q = this.getQueue(guild);
|
|
2401
3164
|
if (!q)
|
|
2402
3165
|
throw new DisTubeError("NO_QUEUE");
|
|
2403
3166
|
return q.shuffle();
|
|
2404
3167
|
}
|
|
3168
|
+
/**
|
|
3169
|
+
* Jump to the song number in the queue.
|
|
3170
|
+
* The next one is 1, 2,...
|
|
3171
|
+
* The previous one is -1, -2,...
|
|
3172
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3173
|
+
* @param {number} num The song number to play
|
|
3174
|
+
* @returns {Promise<Song>} The new Song will be played
|
|
3175
|
+
* @throws {Error} if `num` is invalid number (0 < num < {@link Queue#songs}.length)
|
|
3176
|
+
* @example
|
|
3177
|
+
* client.on('message', (message) => {
|
|
3178
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3179
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3180
|
+
* const command = args.shift();
|
|
3181
|
+
* if (command == "jump")
|
|
3182
|
+
* distube.jump(message, parseInt(args[0]))
|
|
3183
|
+
* .catch(err => message.channel.send("Invalid song number."));
|
|
3184
|
+
* });
|
|
3185
|
+
*/
|
|
2405
3186
|
jump(guild, num) {
|
|
2406
3187
|
const q = this.getQueue(guild);
|
|
2407
3188
|
if (!q)
|
|
2408
3189
|
throw new DisTubeError("NO_QUEUE");
|
|
2409
3190
|
return q.jump(num);
|
|
2410
3191
|
}
|
|
3192
|
+
/**
|
|
3193
|
+
* Set the repeat mode of the guild queue.\
|
|
3194
|
+
* Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
|
|
3195
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3196
|
+
* @param {RepeatMode?} [mode] The repeat modes (toggle if `undefined`)
|
|
3197
|
+
* @returns {RepeatMode} The new repeat mode
|
|
3198
|
+
* @example
|
|
3199
|
+
* client.on('message', (message) => {
|
|
3200
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3201
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3202
|
+
* const command = args.shift();
|
|
3203
|
+
* if (command == "repeat") {
|
|
3204
|
+
* let mode = distube.setRepeatMode(message, parseInt(args[0]));
|
|
3205
|
+
* mode = mode ? mode == 2 ? "Repeat queue" : "Repeat song" : "Off";
|
|
3206
|
+
* message.channel.send("Set repeat mode to `" + mode + "`");
|
|
3207
|
+
* }
|
|
3208
|
+
* });
|
|
3209
|
+
* @example
|
|
3210
|
+
* const { RepeatMode } = require("distube");
|
|
3211
|
+
* let mode;
|
|
3212
|
+
* switch(distube.setRepeatMode(message, parseInt(args[0]))) {
|
|
3213
|
+
* case RepeatMode.DISABLED:
|
|
3214
|
+
* mode = "Off";
|
|
3215
|
+
* break;
|
|
3216
|
+
* case RepeatMode.SONG:
|
|
3217
|
+
* mode = "Repeat a song";
|
|
3218
|
+
* break;
|
|
3219
|
+
* case RepeatMode.QUEUE:
|
|
3220
|
+
* mode = "Repeat all queue";
|
|
3221
|
+
* break;
|
|
3222
|
+
* }
|
|
3223
|
+
* message.channel.send("Set repeat mode to `" + mode + "`");
|
|
3224
|
+
*/
|
|
2411
3225
|
setRepeatMode(guild, mode) {
|
|
2412
3226
|
const q = this.getQueue(guild);
|
|
2413
3227
|
if (!q)
|
|
2414
3228
|
throw new DisTubeError("NO_QUEUE");
|
|
2415
3229
|
return q.setRepeatMode(mode);
|
|
2416
3230
|
}
|
|
3231
|
+
/**
|
|
3232
|
+
* Toggle autoplay mode
|
|
3233
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3234
|
+
* @returns {boolean} Autoplay mode state
|
|
3235
|
+
* @throws {Error}
|
|
3236
|
+
* @example
|
|
3237
|
+
* client.on('message', (message) => {
|
|
3238
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3239
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3240
|
+
* const command = args.shift();
|
|
3241
|
+
* if (command == "autoplay") {
|
|
3242
|
+
* const mode = distube.toggleAutoplay(message);
|
|
3243
|
+
* message.channel.send("Set autoplay mode to `" + (mode ? "On" : "Off") + "`");
|
|
3244
|
+
* }
|
|
3245
|
+
* });
|
|
3246
|
+
*/
|
|
2417
3247
|
toggleAutoplay(guild) {
|
|
2418
3248
|
const q = this.getQueue(guild);
|
|
2419
3249
|
if (!q)
|
|
@@ -2421,18 +3251,43 @@ ${e.message}`;
|
|
|
2421
3251
|
q.autoplay = !q.autoplay;
|
|
2422
3252
|
return q.autoplay;
|
|
2423
3253
|
}
|
|
3254
|
+
/**
|
|
3255
|
+
* Add related song to the queue
|
|
3256
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3257
|
+
* @returns {Promise<Song>} The guild queue
|
|
3258
|
+
*/
|
|
2424
3259
|
addRelatedSong(guild) {
|
|
2425
3260
|
const q = this.getQueue(guild);
|
|
2426
3261
|
if (!q)
|
|
2427
3262
|
throw new DisTubeError("NO_QUEUE");
|
|
2428
3263
|
return q.addRelatedSong();
|
|
2429
3264
|
}
|
|
3265
|
+
/**
|
|
3266
|
+
* Set the playing time to another position
|
|
3267
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3268
|
+
* @param {number} time Time in seconds
|
|
3269
|
+
* @returns {Queue} Seeked queue
|
|
3270
|
+
* @example
|
|
3271
|
+
* client.on('message', message => {
|
|
3272
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3273
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3274
|
+
* const command = args.shift();
|
|
3275
|
+
* if (command = 'seek')
|
|
3276
|
+
* distube.seek(message, Number(args[0]));
|
|
3277
|
+
* });
|
|
3278
|
+
*/
|
|
2430
3279
|
seek(guild, time) {
|
|
2431
3280
|
const q = this.getQueue(guild);
|
|
2432
3281
|
if (!q)
|
|
2433
3282
|
throw new DisTubeError("NO_QUEUE");
|
|
2434
3283
|
return q.seek(time);
|
|
2435
3284
|
}
|
|
3285
|
+
/**
|
|
3286
|
+
* Emit error event
|
|
3287
|
+
* @param {Error} error error
|
|
3288
|
+
* @param {Discord.BaseGuildTextChannel} [channel] Text channel where the error is encountered.
|
|
3289
|
+
* @private
|
|
3290
|
+
*/
|
|
2436
3291
|
emitError(error, channel) {
|
|
2437
3292
|
if (this.listeners("error").length) {
|
|
2438
3293
|
this.emit("error", channel, error);
|
|
@@ -2445,7 +3300,8 @@ ${e.message}`;
|
|
|
2445
3300
|
}
|
|
2446
3301
|
}
|
|
2447
3302
|
};
|
|
2448
|
-
__name(
|
|
3303
|
+
__name(_DisTube, "DisTube");
|
|
3304
|
+
var DisTube = _DisTube;
|
|
2449
3305
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2450
3306
|
0 && (module.exports = {
|
|
2451
3307
|
BaseManager,
|
|
@@ -2485,6 +3341,7 @@ __name(DisTube, "DisTube");
|
|
|
2485
3341
|
isGuildInstance,
|
|
2486
3342
|
isMemberInstance,
|
|
2487
3343
|
isMessageInstance,
|
|
3344
|
+
isNsfwChannel,
|
|
2488
3345
|
isObject,
|
|
2489
3346
|
isRecord,
|
|
2490
3347
|
isSnowflake,
|