distube 4.0.4 → 4.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -8
- package/dist/index.d.ts +36 -33
- package/dist/index.js +1081 -210
- package/dist/index.js.map +1 -1
- package/package.json +32 -30
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.5",
|
|
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.12",
|
|
123
128
|
"@distube/ytpl": "^1.1.1",
|
|
124
|
-
"@distube/ytsr": "^1.1.
|
|
129
|
+
"@distube/ytsr": "^1.1.9",
|
|
125
130
|
"prism-media": "https://codeload.github.com/distubejs/prism-media/tar.gz/main#workaround.tar.gz",
|
|
126
131
|
"tiny-typed-emitter": "^2.1.0",
|
|
127
|
-
tslib: "^2.
|
|
128
|
-
undici: "^5.
|
|
132
|
+
tslib: "^2.6.0",
|
|
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.2",
|
|
148
|
+
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
|
149
|
+
"@typescript-eslint/parser": "^6.1.0",
|
|
150
|
+
"babel-jest": "^29.6.1",
|
|
151
|
+
"discord.js": "^14.11.0",
|
|
152
|
+
eslint: "^8.45.0",
|
|
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.8.0",
|
|
155
|
+
"eslint-plugin-deprecation": "^1.4.1",
|
|
156
|
+
"eslint-plugin-jsdoc": "^46.4.4",
|
|
157
|
+
husky: "^8.0.3",
|
|
158
|
+
jest: "^29.6.1",
|
|
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.16",
|
|
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,107 @@ 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
|
+
}
|
|
1163
|
+
if (typeof options.seek === "number" && options.seek > 0) {
|
|
1164
|
+
args.unshift("-ss", options.seek.toString());
|
|
1165
|
+
}
|
|
1166
|
+
if (Array.isArray(options.ffmpegArgs)) {
|
|
1167
|
+
args.push(...options.ffmpegArgs);
|
|
1168
|
+
}
|
|
1169
|
+
this.stream = new import_prism_media.FFmpeg({ args, shell: false });
|
|
1170
|
+
this.stream._readableState && (this.stream._readableState.highWaterMark = 1 << 25);
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Create a stream from ytdl video formats
|
|
1174
|
+
* @param {ytdl.videoFormat[]} formats ytdl video formats
|
|
1175
|
+
* @param {StreamOptions} options options
|
|
1176
|
+
* @returns {DisTubeStream}
|
|
1177
|
+
* @private
|
|
1178
|
+
*/
|
|
1179
|
+
static YouTube(formats, options = {}) {
|
|
1180
|
+
if (!formats || !formats.length)
|
|
1181
|
+
throw new DisTubeError("UNAVAILABLE_VIDEO");
|
|
1182
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
1183
|
+
throw new DisTubeError("INVALID_TYPE", "object", options, "options");
|
|
1184
|
+
}
|
|
1185
|
+
const bestFormat = chooseBestVideoFormat(formats, options.isLive);
|
|
1186
|
+
if (!bestFormat)
|
|
1187
|
+
throw new DisTubeError("UNPLAYABLE_FORMATS");
|
|
1188
|
+
return new _DisTubeStream(bestFormat.url, options);
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* Create a stream from a stream url
|
|
1192
|
+
* @param {string} url stream url
|
|
1193
|
+
* @param {StreamOptions} options options
|
|
1194
|
+
* @returns {DisTubeStream}
|
|
1195
|
+
* @private
|
|
1196
|
+
*/
|
|
1197
|
+
static DirectLink(url, options = {}) {
|
|
1198
|
+
if (!options || typeof options !== "object" || Array.isArray(options)) {
|
|
1199
|
+
throw new DisTubeError("INVALID_TYPE", "object", options, "options");
|
|
1200
|
+
}
|
|
1201
|
+
if (typeof url !== "string" || !isURL(url)) {
|
|
1202
|
+
throw new DisTubeError("INVALID_TYPE", "an URL", url);
|
|
1203
|
+
}
|
|
1204
|
+
return new _DisTubeStream(url, options);
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
__name(_DisTubeStream, "DisTubeStream");
|
|
1208
|
+
var DisTubeStream = _DisTubeStream;
|
|
925
1209
|
|
|
926
1210
|
// src/core/DisTubeHandler.ts
|
|
927
1211
|
var import_ytpl = __toESM(require("@distube/ytpl"));
|
|
928
1212
|
var import_ytdl_core = __toESM(require("@distube/ytdl-core"));
|
|
929
|
-
var
|
|
1213
|
+
var _DisTubeHandler = class _DisTubeHandler extends DisTubeBase {
|
|
930
1214
|
constructor(distube) {
|
|
931
1215
|
super(distube);
|
|
932
1216
|
const client = this.client;
|
|
@@ -976,11 +1260,23 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
976
1260
|
}
|
|
977
1261
|
return options;
|
|
978
1262
|
}
|
|
1263
|
+
/**
|
|
1264
|
+
* @param {string} url url
|
|
1265
|
+
* @param {boolean} [basic=false] getBasicInfo?
|
|
1266
|
+
* @returns {Promise<ytdl.videoInfo>}
|
|
1267
|
+
*/
|
|
979
1268
|
getYouTubeInfo(url, basic = false) {
|
|
980
1269
|
if (basic)
|
|
981
1270
|
return import_ytdl_core.default.getBasicInfo(url, this.ytdlOptions);
|
|
982
1271
|
return import_ytdl_core.default.getInfo(url, this.ytdlOptions);
|
|
983
1272
|
}
|
|
1273
|
+
/**
|
|
1274
|
+
* Resolve a url or a supported object to a {@link Song} or {@link Playlist}
|
|
1275
|
+
* @param {string|Song|SearchResult|Playlist} song URL | {@link Song}| {@link SearchResult} | {@link Playlist}
|
|
1276
|
+
* @param {ResolveOptions} [options] Optional options
|
|
1277
|
+
* @returns {Promise<Song|Playlist|null>} Resolved
|
|
1278
|
+
* @throws {DisTubeError}
|
|
1279
|
+
*/
|
|
984
1280
|
async resolve(song, options = {}) {
|
|
985
1281
|
if (song instanceof Song || song instanceof Playlist) {
|
|
986
1282
|
if ("metadata" in options)
|
|
@@ -1011,6 +1307,12 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1011
1307
|
}
|
|
1012
1308
|
throw new DisTubeError("CANNOT_RESOLVE_SONG", song);
|
|
1013
1309
|
}
|
|
1310
|
+
/**
|
|
1311
|
+
* Resolve Song[] or YouTube playlist url to a Playlist
|
|
1312
|
+
* @param {Playlist|Song[]|string} playlist Resolvable playlist
|
|
1313
|
+
* @param {ResolvePlaylistOptions} options Optional options
|
|
1314
|
+
* @returns {Promise<Playlist>}
|
|
1315
|
+
*/
|
|
1014
1316
|
async resolvePlaylist(playlist, options = {}) {
|
|
1015
1317
|
const { member, source, metadata } = { source: "youtube", ...options };
|
|
1016
1318
|
if (playlist instanceof Playlist) {
|
|
@@ -1037,6 +1339,13 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1037
1339
|
}
|
|
1038
1340
|
return new Playlist(playlist, { member, properties: { source }, metadata });
|
|
1039
1341
|
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Search for a song, fire {@link DisTube#event:error} if not found.
|
|
1344
|
+
* @param {Discord.Message} message The original message from an user
|
|
1345
|
+
* @param {string} query The query string
|
|
1346
|
+
* @returns {Promise<SearchResult?>} Song info
|
|
1347
|
+
* @throws {DisTubeError}
|
|
1348
|
+
*/
|
|
1040
1349
|
async searchSong(message, query) {
|
|
1041
1350
|
if (!isMessageInstance(message))
|
|
1042
1351
|
throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
|
|
@@ -1047,7 +1356,7 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1047
1356
|
const limit = this.options.searchSongs > 1 ? this.options.searchSongs : 1;
|
|
1048
1357
|
const results = await this.distube.search(query, {
|
|
1049
1358
|
limit,
|
|
1050
|
-
safeSearch: this.options.nsfw ? false : !message.channel
|
|
1359
|
+
safeSearch: this.options.nsfw ? false : !isNsfwChannel(message.channel)
|
|
1051
1360
|
}).catch(() => {
|
|
1052
1361
|
if (!this.emit("searchNoResult", message, query)) {
|
|
1053
1362
|
console.warn("searchNoResult event does not have any listeners! Emits `error` event instead.");
|
|
@@ -1058,6 +1367,17 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1058
1367
|
return null;
|
|
1059
1368
|
return this.createSearchMessageCollector(message, results, query);
|
|
1060
1369
|
}
|
|
1370
|
+
/**
|
|
1371
|
+
* Create a message collector for selecting search results.
|
|
1372
|
+
*
|
|
1373
|
+
* Needed events: {@link DisTube#event:searchResult}, {@link DisTube#event:searchCancel},
|
|
1374
|
+
* {@link DisTube#event:searchInvalidAnswer}, {@link DisTube#event:searchDone}.
|
|
1375
|
+
* @param {Discord.Message} message The original message from an user
|
|
1376
|
+
* @param {Array<SearchResult|Song|Playlist>} results The search results
|
|
1377
|
+
* @param {string?} [query] The query string
|
|
1378
|
+
* @returns {Promise<SearchResult|Song|Playlist|null>} Selected result
|
|
1379
|
+
* @throws {DisTubeError}
|
|
1380
|
+
*/
|
|
1061
1381
|
async createSearchMessageCollector(message, results, query) {
|
|
1062
1382
|
if (!isMessageInstance(message))
|
|
1063
1383
|
throw new DisTubeError("INVALID_TYPE", "Discord.Message", message, "message");
|
|
@@ -1110,19 +1430,26 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1110
1430
|
}
|
|
1111
1431
|
return result;
|
|
1112
1432
|
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Play or add a {@link Playlist} to the queue.
|
|
1435
|
+
* @param {Discord.BaseGuildVoiceChannel} voiceChannel A voice channel
|
|
1436
|
+
* @param {Playlist|string} playlist A YouTube playlist url | a Playlist
|
|
1437
|
+
* @param {PlayHandlerOptions} [options] Optional options
|
|
1438
|
+
* @returns {Promise<void>}
|
|
1439
|
+
* @throws {DisTubeError}
|
|
1440
|
+
*/
|
|
1113
1441
|
async playPlaylist(voiceChannel, playlist, options = {}) {
|
|
1114
1442
|
const { textChannel, skip } = { skip: false, ...options };
|
|
1115
1443
|
const position = Number(options.position) || (skip ? 1 : 0);
|
|
1116
1444
|
if (!(playlist instanceof Playlist))
|
|
1117
1445
|
throw new DisTubeError("INVALID_TYPE", "Playlist", playlist, "playlist");
|
|
1118
1446
|
const queue = this.queues.get(voiceChannel);
|
|
1119
|
-
|
|
1447
|
+
const isNsfw = isNsfwChannel(queue?.textChannel || textChannel);
|
|
1448
|
+
if (!this.options.nsfw && !isNsfw)
|
|
1120
1449
|
playlist.songs = playlist.songs.filter((s) => !s.age_restricted);
|
|
1121
|
-
}
|
|
1122
1450
|
if (!playlist.songs.length) {
|
|
1123
|
-
if (!this.options.nsfw && !
|
|
1451
|
+
if (!this.options.nsfw && !isNsfw)
|
|
1124
1452
|
throw new DisTubeError("EMPTY_FILTERED_PLAYLIST");
|
|
1125
|
-
}
|
|
1126
1453
|
throw new DisTubeError("EMPTY_PLAYLIST");
|
|
1127
1454
|
}
|
|
1128
1455
|
if (queue) {
|
|
@@ -1142,13 +1469,21 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1142
1469
|
}
|
|
1143
1470
|
}
|
|
1144
1471
|
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Play or add a {@link Song} to the queue.
|
|
1474
|
+
* @param {Discord.BaseGuildVoiceChannel} voiceChannel A voice channel
|
|
1475
|
+
* @param {Song} song A YouTube playlist url | a Playlist
|
|
1476
|
+
* @param {PlayHandlerOptions} [options] Optional options
|
|
1477
|
+
* @returns {Promise<void>}
|
|
1478
|
+
* @throws {DisTubeError}
|
|
1479
|
+
*/
|
|
1145
1480
|
async playSong(voiceChannel, song, options = {}) {
|
|
1146
1481
|
if (!(song instanceof Song))
|
|
1147
1482
|
throw new DisTubeError("INVALID_TYPE", "Song", song, "song");
|
|
1148
1483
|
const { textChannel, skip } = { skip: false, ...options };
|
|
1149
1484
|
const position = Number(options.position) || (skip ? 1 : 0);
|
|
1150
1485
|
const queue = this.queues.get(voiceChannel);
|
|
1151
|
-
if (!this.options.nsfw && song.age_restricted && !(queue?.textChannel || textChannel)
|
|
1486
|
+
if (!this.options.nsfw && song.age_restricted && !isNsfwChannel(queue?.textChannel || textChannel)) {
|
|
1152
1487
|
throw new DisTubeError("NON_NSFW");
|
|
1153
1488
|
}
|
|
1154
1489
|
if (queue) {
|
|
@@ -1168,12 +1503,35 @@ var DisTubeHandler = class extends DisTubeBase {
|
|
|
1168
1503
|
}
|
|
1169
1504
|
}
|
|
1170
1505
|
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Get {@link Song}'s stream info and attach it to the song.
|
|
1508
|
+
* @param {Song} song A Song
|
|
1509
|
+
*/
|
|
1510
|
+
async attachStreamInfo(song) {
|
|
1511
|
+
const { url, source, formats, streamURL, isLive } = song;
|
|
1512
|
+
if (source === "youtube") {
|
|
1513
|
+
if (!formats || !chooseBestVideoFormat(formats, isLive)) {
|
|
1514
|
+
song._patchYouTube(await this.handler.getYouTubeInfo(url));
|
|
1515
|
+
}
|
|
1516
|
+
} else if (!streamURL) {
|
|
1517
|
+
for (const plugin of [...this.distube.extractorPlugins, ...this.distube.customPlugins]) {
|
|
1518
|
+
if (await plugin.validate(url)) {
|
|
1519
|
+
const info = [plugin.getStreamURL(url), plugin.getRelatedSongs(url)];
|
|
1520
|
+
const result = await Promise.all(info);
|
|
1521
|
+
song.streamURL = result[0];
|
|
1522
|
+
song.related = result[1];
|
|
1523
|
+
break;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1171
1528
|
};
|
|
1172
|
-
__name(
|
|
1529
|
+
__name(_DisTubeHandler, "DisTubeHandler");
|
|
1530
|
+
var DisTubeHandler = _DisTubeHandler;
|
|
1173
1531
|
|
|
1174
1532
|
// src/core/DisTubeOptions.ts
|
|
1175
1533
|
var _validateOptions, validateOptions_fn;
|
|
1176
|
-
var
|
|
1534
|
+
var _Options = class _Options {
|
|
1177
1535
|
constructor(options) {
|
|
1178
1536
|
__privateAdd(this, _validateOptions);
|
|
1179
1537
|
__publicField(this, "plugins");
|
|
@@ -1222,7 +1580,6 @@ var Options = class {
|
|
|
1222
1580
|
__privateMethod(this, _validateOptions, validateOptions_fn).call(this);
|
|
1223
1581
|
}
|
|
1224
1582
|
};
|
|
1225
|
-
__name(Options, "Options");
|
|
1226
1583
|
_validateOptions = new WeakSet();
|
|
1227
1584
|
validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
|
|
1228
1585
|
if (typeof options.emitNewSongOnly !== "boolean") {
|
|
@@ -1303,95 +1660,34 @@ validateOptions_fn = /* @__PURE__ */ __name(function(options = this) {
|
|
|
1303
1660
|
throw new DisTubeError("INVALID_TYPE", "boolean", options.directLink, "DisTubeOptions.directLink");
|
|
1304
1661
|
}
|
|
1305
1662
|
}, "#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");
|
|
1663
|
+
__name(_Options, "Options");
|
|
1664
|
+
var Options = _Options;
|
|
1379
1665
|
|
|
1380
1666
|
// src/core/manager/BaseManager.ts
|
|
1381
1667
|
var import_discord2 = require("discord.js");
|
|
1382
|
-
var
|
|
1668
|
+
var _BaseManager = class _BaseManager extends DisTubeBase {
|
|
1383
1669
|
constructor() {
|
|
1384
1670
|
super(...arguments);
|
|
1671
|
+
/**
|
|
1672
|
+
* The collection of items for this manager.
|
|
1673
|
+
* @type {Collection}
|
|
1674
|
+
* @name BaseManager#collection
|
|
1675
|
+
*/
|
|
1385
1676
|
__publicField(this, "collection", new import_discord2.Collection());
|
|
1386
1677
|
}
|
|
1678
|
+
/**
|
|
1679
|
+
* The size of the collection.
|
|
1680
|
+
* @type {number}
|
|
1681
|
+
*/
|
|
1387
1682
|
get size() {
|
|
1388
1683
|
return this.collection.size;
|
|
1389
1684
|
}
|
|
1390
1685
|
};
|
|
1391
|
-
__name(
|
|
1686
|
+
__name(_BaseManager, "BaseManager");
|
|
1687
|
+
var BaseManager = _BaseManager;
|
|
1392
1688
|
|
|
1393
1689
|
// src/core/manager/GuildIdManager.ts
|
|
1394
|
-
var
|
|
1690
|
+
var _GuildIdManager = class _GuildIdManager extends BaseManager {
|
|
1395
1691
|
add(idOrInstance, data) {
|
|
1396
1692
|
const id = resolveGuildId(idOrInstance);
|
|
1397
1693
|
const existing = this.get(id);
|
|
@@ -1409,11 +1705,30 @@ var GuildIdManager = class extends BaseManager {
|
|
|
1409
1705
|
return this.collection.has(resolveGuildId(idOrInstance));
|
|
1410
1706
|
}
|
|
1411
1707
|
};
|
|
1412
|
-
__name(
|
|
1708
|
+
__name(_GuildIdManager, "GuildIdManager");
|
|
1709
|
+
var GuildIdManager = _GuildIdManager;
|
|
1413
1710
|
|
|
1414
1711
|
// src/core/manager/DisTubeVoiceManager.ts
|
|
1415
1712
|
var import_voice3 = require("@discordjs/voice");
|
|
1416
|
-
var
|
|
1713
|
+
var _DisTubeVoiceManager = class _DisTubeVoiceManager extends GuildIdManager {
|
|
1714
|
+
/**
|
|
1715
|
+
* Get a {@link DisTubeVoice}.
|
|
1716
|
+
* @method get
|
|
1717
|
+
* @memberof DisTubeVoiceManager#
|
|
1718
|
+
* @param {GuildIdResolvable} guild The queue resolvable to resolve
|
|
1719
|
+
* @returns {DisTubeVoice?}
|
|
1720
|
+
*/
|
|
1721
|
+
/**
|
|
1722
|
+
* Collection of {@link DisTubeVoice}.
|
|
1723
|
+
* @name DisTubeVoiceManager#collection
|
|
1724
|
+
* @type {Discord.Collection<string, DisTubeVoice>}
|
|
1725
|
+
*/
|
|
1726
|
+
/**
|
|
1727
|
+
* Create a {@link DisTubeVoice}
|
|
1728
|
+
* @param {Discord.BaseGuildVoiceChannel} channel A voice channel to join
|
|
1729
|
+
* @returns {DisTubeVoice}
|
|
1730
|
+
* @private
|
|
1731
|
+
*/
|
|
1417
1732
|
create(channel) {
|
|
1418
1733
|
const existing = this.get(channel.guildId);
|
|
1419
1734
|
if (existing) {
|
|
@@ -1422,12 +1737,21 @@ var DisTubeVoiceManager = class extends GuildIdManager {
|
|
|
1422
1737
|
}
|
|
1423
1738
|
return new DisTubeVoice(this, channel);
|
|
1424
1739
|
}
|
|
1740
|
+
/**
|
|
1741
|
+
* Join a voice channel
|
|
1742
|
+
* @param {Discord.BaseGuildVoiceChannel} channel A voice channel to join
|
|
1743
|
+
* @returns {Promise<DisTubeVoice>}
|
|
1744
|
+
*/
|
|
1425
1745
|
join(channel) {
|
|
1426
1746
|
const existing = this.get(channel.guildId);
|
|
1427
1747
|
if (existing)
|
|
1428
1748
|
return existing.join(channel);
|
|
1429
1749
|
return this.create(channel).join();
|
|
1430
1750
|
}
|
|
1751
|
+
/**
|
|
1752
|
+
* Leave the connected voice channel in a guild
|
|
1753
|
+
* @param {GuildIdResolvable} guild Queue Resolvable
|
|
1754
|
+
*/
|
|
1431
1755
|
leave(guild) {
|
|
1432
1756
|
const voice = this.get(guild);
|
|
1433
1757
|
if (voice) {
|
|
@@ -1440,20 +1764,33 @@ var DisTubeVoiceManager = class extends GuildIdManager {
|
|
|
1440
1764
|
}
|
|
1441
1765
|
}
|
|
1442
1766
|
};
|
|
1443
|
-
__name(
|
|
1767
|
+
__name(_DisTubeVoiceManager, "DisTubeVoiceManager");
|
|
1768
|
+
var DisTubeVoiceManager = _DisTubeVoiceManager;
|
|
1444
1769
|
|
|
1445
1770
|
// src/core/manager/FilterManager.ts
|
|
1446
|
-
var _validate, validate_fn, _resolveName, resolveName_fn, _resolveValue, resolveValue_fn, _apply, apply_fn;
|
|
1447
|
-
var
|
|
1771
|
+
var _validate, validate_fn, _resolveName, resolveName_fn, _resolveValue, resolveValue_fn, _apply, apply_fn, _removeFn, removeFn_get;
|
|
1772
|
+
var _FilterManager = class _FilterManager extends BaseManager {
|
|
1448
1773
|
constructor(queue) {
|
|
1449
1774
|
super(queue.distube);
|
|
1450
1775
|
__privateAdd(this, _validate);
|
|
1451
1776
|
__privateAdd(this, _resolveName);
|
|
1452
1777
|
__privateAdd(this, _resolveValue);
|
|
1453
1778
|
__privateAdd(this, _apply);
|
|
1779
|
+
__privateAdd(this, _removeFn);
|
|
1780
|
+
/**
|
|
1781
|
+
* Collection of {@link FilterResolvable}.
|
|
1782
|
+
* @name FilterManager#collection
|
|
1783
|
+
* @type {Discord.Collection<string, DisTubeVoice>}
|
|
1784
|
+
*/
|
|
1454
1785
|
__publicField(this, "queue");
|
|
1455
1786
|
this.queue = queue;
|
|
1456
1787
|
}
|
|
1788
|
+
/**
|
|
1789
|
+
* Enable a filter or multiple filters to the manager
|
|
1790
|
+
* @param {FilterResolvable|FilterResolvable[]} filterOrFilters The filter or filters to enable
|
|
1791
|
+
* @param {boolean} [override=false] Wether or not override the applied filter with new filter value
|
|
1792
|
+
* @returns {FilterManager}
|
|
1793
|
+
*/
|
|
1457
1794
|
add(filterOrFilters, override = false) {
|
|
1458
1795
|
if (Array.isArray(filterOrFilters)) {
|
|
1459
1796
|
const resolvedFilters = filterOrFilters.map((f) => __privateMethod(this, _validate, validate_fn).call(this, f));
|
|
@@ -1469,13 +1806,31 @@ var FilterManager = class extends BaseManager {
|
|
|
1469
1806
|
return unique;
|
|
1470
1807
|
}, []).reverse();
|
|
1471
1808
|
return this.set([...this.collection.values(), ...newFilters]);
|
|
1809
|
+
} else if (typeof filterOrFilters === "string") {
|
|
1810
|
+
return this.set([...this.collection.values(), filterOrFilters]);
|
|
1472
1811
|
}
|
|
1473
|
-
|
|
1812
|
+
throw new DisTubeError(
|
|
1813
|
+
"INVALID_TYPE",
|
|
1814
|
+
["FilterResolvable", "Array<FilterResolvable>"],
|
|
1815
|
+
filterOrFilters,
|
|
1816
|
+
"filterOrFilters"
|
|
1817
|
+
);
|
|
1474
1818
|
}
|
|
1819
|
+
/**
|
|
1820
|
+
* Clear enabled filters of the manager
|
|
1821
|
+
* @returns {FilterManager}
|
|
1822
|
+
*/
|
|
1475
1823
|
clear() {
|
|
1476
1824
|
return this.set([]);
|
|
1477
1825
|
}
|
|
1826
|
+
/**
|
|
1827
|
+
* Set the filters applied to the manager
|
|
1828
|
+
* @param {FilterResolvable[]} filters The filters to apply
|
|
1829
|
+
* @returns {FilterManager}
|
|
1830
|
+
*/
|
|
1478
1831
|
set(filters) {
|
|
1832
|
+
if (!Array.isArray(filters))
|
|
1833
|
+
throw new DisTubeError("INVALID_TYPE", "Array<FilterResolvable>", filters, "filters");
|
|
1479
1834
|
this.collection.clear();
|
|
1480
1835
|
for (const filter of filters) {
|
|
1481
1836
|
const resolved = __privateMethod(this, _validate, validate_fn).call(this, filter);
|
|
@@ -1484,18 +1839,40 @@ var FilterManager = class extends BaseManager {
|
|
|
1484
1839
|
__privateMethod(this, _apply, apply_fn).call(this);
|
|
1485
1840
|
return this;
|
|
1486
1841
|
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Disable a filter or multiple filters
|
|
1844
|
+
* @param {FilterResolvable|FilterResolvable[]} filterOrFilters The filter or filters to disable
|
|
1845
|
+
* @returns {FilterManager}
|
|
1846
|
+
*/
|
|
1487
1847
|
remove(filterOrFilters) {
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1848
|
+
if (Array.isArray(filterOrFilters)) {
|
|
1849
|
+
filterOrFilters.map(__privateGet(this, _removeFn, removeFn_get));
|
|
1850
|
+
} else if (typeof filterOrFilters === "string") {
|
|
1851
|
+
__privateGet(this, _removeFn, removeFn_get).call(this, filterOrFilters);
|
|
1852
|
+
} else {
|
|
1853
|
+
throw new DisTubeError(
|
|
1854
|
+
"INVALID_TYPE",
|
|
1855
|
+
["FilterResolvable", "Array<FilterResolvable>"],
|
|
1856
|
+
filterOrFilters,
|
|
1857
|
+
"filterOrFilters"
|
|
1858
|
+
);
|
|
1859
|
+
}
|
|
1493
1860
|
__privateMethod(this, _apply, apply_fn).call(this);
|
|
1494
1861
|
return this;
|
|
1495
1862
|
}
|
|
1863
|
+
/**
|
|
1864
|
+
* Check whether a filter enabled or not
|
|
1865
|
+
* @param {FilterResolvable} filter The filter to check
|
|
1866
|
+
* @returns {boolean}
|
|
1867
|
+
*/
|
|
1496
1868
|
has(filter) {
|
|
1497
1869
|
return this.collection.has(__privateMethod(this, _resolveName, resolveName_fn).call(this, filter));
|
|
1498
1870
|
}
|
|
1871
|
+
/**
|
|
1872
|
+
* Array of enabled filter name
|
|
1873
|
+
* @type {Array<string>}
|
|
1874
|
+
* @readonly
|
|
1875
|
+
*/
|
|
1499
1876
|
get names() {
|
|
1500
1877
|
return this.collection.map((f) => __privateMethod(this, _resolveName, resolveName_fn).call(this, f));
|
|
1501
1878
|
}
|
|
@@ -1506,7 +1883,6 @@ var FilterManager = class extends BaseManager {
|
|
|
1506
1883
|
return this.names.toString();
|
|
1507
1884
|
}
|
|
1508
1885
|
};
|
|
1509
|
-
__name(FilterManager, "FilterManager");
|
|
1510
1886
|
_validate = new WeakSet();
|
|
1511
1887
|
validate_fn = /* @__PURE__ */ __name(function(filter) {
|
|
1512
1888
|
if (typeof filter === "string" && Object.prototype.hasOwnProperty.call(this.distube.filters, filter) || typeof filter === "object" && typeof filter.name === "string" && typeof filter.value === "string") {
|
|
@@ -1527,17 +1903,66 @@ apply_fn = /* @__PURE__ */ __name(function() {
|
|
|
1527
1903
|
this.queue.beginTime = this.queue.currentTime;
|
|
1528
1904
|
this.queues.playSong(this.queue);
|
|
1529
1905
|
}, "#apply");
|
|
1906
|
+
_removeFn = new WeakSet();
|
|
1907
|
+
removeFn_get = /* @__PURE__ */ __name(function() {
|
|
1908
|
+
return (f) => this.collection.delete(__privateMethod(this, _resolveName, resolveName_fn).call(this, __privateMethod(this, _validate, validate_fn).call(this, f)));
|
|
1909
|
+
}, "#removeFn");
|
|
1910
|
+
__name(_FilterManager, "FilterManager");
|
|
1911
|
+
var FilterManager = _FilterManager;
|
|
1530
1912
|
|
|
1531
1913
|
// src/core/manager/QueueManager.ts
|
|
1532
|
-
var _voiceEventHandler, voiceEventHandler_fn, _handleSongFinish, handleSongFinish_fn, _handlePlayingError, handlePlayingError_fn
|
|
1533
|
-
var
|
|
1914
|
+
var _voiceEventHandler, voiceEventHandler_fn, _emitPlaySong, emitPlaySong_fn, _handleSongFinish, handleSongFinish_fn, _handlePlayingError, handlePlayingError_fn;
|
|
1915
|
+
var _QueueManager = class _QueueManager extends GuildIdManager {
|
|
1534
1916
|
constructor() {
|
|
1535
1917
|
super(...arguments);
|
|
1918
|
+
/**
|
|
1919
|
+
* Get a Queue from this QueueManager.
|
|
1920
|
+
* @method get
|
|
1921
|
+
* @memberof QueueManager#
|
|
1922
|
+
* @param {GuildIdResolvable} guild Resolvable thing from a guild
|
|
1923
|
+
* @returns {Queue?}
|
|
1924
|
+
*/
|
|
1925
|
+
/**
|
|
1926
|
+
* Listen to DisTubeVoice events and handle the Queue
|
|
1927
|
+
* @private
|
|
1928
|
+
* @param {Queue} queue Queue
|
|
1929
|
+
*/
|
|
1536
1930
|
__privateAdd(this, _voiceEventHandler);
|
|
1931
|
+
/**
|
|
1932
|
+
* Whether or not emit playSong event
|
|
1933
|
+
* @param {Queue} queue Queue
|
|
1934
|
+
* @private
|
|
1935
|
+
* @returns {boolean}
|
|
1936
|
+
*/
|
|
1937
|
+
__privateAdd(this, _emitPlaySong);
|
|
1938
|
+
/**
|
|
1939
|
+
* Handle the queue when a Song finish
|
|
1940
|
+
* @private
|
|
1941
|
+
* @param {Queue} queue queue
|
|
1942
|
+
* @returns {Promise<void>}
|
|
1943
|
+
*/
|
|
1537
1944
|
__privateAdd(this, _handleSongFinish);
|
|
1945
|
+
/**
|
|
1946
|
+
* Handle error while playing
|
|
1947
|
+
* @private
|
|
1948
|
+
* @param {Queue} queue queue
|
|
1949
|
+
* @param {Error} error error
|
|
1950
|
+
*/
|
|
1538
1951
|
__privateAdd(this, _handlePlayingError);
|
|
1539
|
-
__privateAdd(this, _emitPlaySong);
|
|
1540
1952
|
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Collection of {@link Queue}.
|
|
1955
|
+
* @name QueueManager#collection
|
|
1956
|
+
* @type {Discord.Collection<string, Queue>}
|
|
1957
|
+
*/
|
|
1958
|
+
/**
|
|
1959
|
+
* Create a {@link Queue}
|
|
1960
|
+
* @private
|
|
1961
|
+
* @param {Discord.BaseGuildVoiceChannel} channel A voice channel
|
|
1962
|
+
* @param {Song|Song[]} song First song
|
|
1963
|
+
* @param {Discord.BaseGuildTextChannel} textChannel Default text channel
|
|
1964
|
+
* @returns {Promise<Queue|true>} Returns `true` if encounter an error
|
|
1965
|
+
*/
|
|
1541
1966
|
async create(channel, song, textChannel) {
|
|
1542
1967
|
if (this.has(channel.guildId))
|
|
1543
1968
|
throw new DisTubeError("QUEUE_EXIST");
|
|
@@ -1555,6 +1980,11 @@ var QueueManager = class extends GuildIdManager {
|
|
|
1555
1980
|
queue._taskQueue.resolve();
|
|
1556
1981
|
}
|
|
1557
1982
|
}
|
|
1983
|
+
/**
|
|
1984
|
+
* Create a ytdl stream
|
|
1985
|
+
* @param {Queue} queue Queue
|
|
1986
|
+
* @returns {DisTubeStream}
|
|
1987
|
+
*/
|
|
1558
1988
|
createStream(queue) {
|
|
1559
1989
|
const { duration, formats, isLive, source, streamURL } = queue.songs[0];
|
|
1560
1990
|
const ffmpegArgs = queue.filters.size ? ["-af", queue.filters.values.join(",")] : void 0;
|
|
@@ -1564,40 +1994,29 @@ var QueueManager = class extends GuildIdManager {
|
|
|
1564
1994
|
return DisTubeStream.YouTube(formats, streamOptions);
|
|
1565
1995
|
return DisTubeStream.DirectLink(streamURL, streamOptions);
|
|
1566
1996
|
}
|
|
1997
|
+
/**
|
|
1998
|
+
* Play a song on voice connection
|
|
1999
|
+
* @private
|
|
2000
|
+
* @param {Queue} queue The guild queue
|
|
2001
|
+
* @returns {Promise<boolean>} error?
|
|
2002
|
+
*/
|
|
1567
2003
|
async playSong(queue) {
|
|
1568
2004
|
if (!queue)
|
|
1569
2005
|
return true;
|
|
1570
|
-
if (!queue.songs.length) {
|
|
2006
|
+
if (queue.stopped || !queue.songs.length) {
|
|
1571
2007
|
queue.stop();
|
|
1572
2008
|
return true;
|
|
1573
2009
|
}
|
|
1574
|
-
if (queue.stopped)
|
|
1575
|
-
return false;
|
|
1576
2010
|
try {
|
|
1577
2011
|
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
|
-
}
|
|
2012
|
+
await this.handler.attachStreamInfo(song);
|
|
2013
|
+
if (queue.stopped || !queue.songs.length) {
|
|
2014
|
+
queue.stop();
|
|
2015
|
+
return true;
|
|
1593
2016
|
}
|
|
1594
2017
|
const stream = this.createStream(queue);
|
|
1595
2018
|
queue.voice.play(stream);
|
|
1596
2019
|
song.streamURL = stream.url;
|
|
1597
|
-
if (queue.stopped)
|
|
1598
|
-
queue.stop();
|
|
1599
|
-
else if (queue.paused)
|
|
1600
|
-
queue.voice.pause();
|
|
1601
2020
|
return false;
|
|
1602
2021
|
} catch (e) {
|
|
1603
2022
|
__privateMethod(this, _handlePlayingError, handlePlayingError_fn).call(this, queue, e);
|
|
@@ -1605,7 +2024,6 @@ var QueueManager = class extends GuildIdManager {
|
|
|
1605
2024
|
}
|
|
1606
2025
|
}
|
|
1607
2026
|
};
|
|
1608
|
-
__name(QueueManager, "QueueManager");
|
|
1609
2027
|
_voiceEventHandler = new WeakSet();
|
|
1610
2028
|
voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
|
|
1611
2029
|
queue._listeners = {
|
|
@@ -1622,6 +2040,10 @@ voiceEventHandler_fn = /* @__PURE__ */ __name(function(queue) {
|
|
|
1622
2040
|
queue.voice.on(event, queue._listeners[event]);
|
|
1623
2041
|
}
|
|
1624
2042
|
}, "#voiceEventHandler");
|
|
2043
|
+
_emitPlaySong = new WeakSet();
|
|
2044
|
+
emitPlaySong_fn = /* @__PURE__ */ __name(function(queue) {
|
|
2045
|
+
return !this.options.emitNewSongOnly || queue.repeatMode === 1 /* SONG */ && queue._next || queue.repeatMode !== 1 /* SONG */ && queue.songs[0]?.id !== queue.songs[1]?.id;
|
|
2046
|
+
}, "#emitPlaySong");
|
|
1625
2047
|
_handleSongFinish = new WeakSet();
|
|
1626
2048
|
handleSongFinish_fn = /* @__PURE__ */ __name(async function(queue) {
|
|
1627
2049
|
this.emit("finishSong", queue, queue.songs[0]);
|
|
@@ -1695,14 +2117,19 @@ Name: ${song.name}`;
|
|
|
1695
2117
|
queue.stop();
|
|
1696
2118
|
}
|
|
1697
2119
|
}, "#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");
|
|
2120
|
+
__name(_QueueManager, "QueueManager");
|
|
2121
|
+
var QueueManager = _QueueManager;
|
|
1702
2122
|
|
|
1703
2123
|
// src/struct/Queue.ts
|
|
1704
2124
|
var _filters;
|
|
1705
|
-
var
|
|
2125
|
+
var _Queue = class _Queue extends DisTubeBase {
|
|
2126
|
+
/**
|
|
2127
|
+
* Create a queue for the guild
|
|
2128
|
+
* @param {DisTube} distube DisTube
|
|
2129
|
+
* @param {DisTubeVoice} voice Voice connection
|
|
2130
|
+
* @param {Song|Song[]} song First song(s)
|
|
2131
|
+
* @param {Discord.BaseGuildTextChannel?} textChannel Default text channel
|
|
2132
|
+
*/
|
|
1706
2133
|
constructor(distube, voice, song, textChannel) {
|
|
1707
2134
|
super(distube);
|
|
1708
2135
|
__publicField(this, "id");
|
|
@@ -1741,24 +2168,58 @@ var Queue = class extends DisTubeBase {
|
|
|
1741
2168
|
this._taskQueue = new TaskQueue();
|
|
1742
2169
|
this._listeners = void 0;
|
|
1743
2170
|
}
|
|
2171
|
+
/**
|
|
2172
|
+
* The client user as a `GuildMember` of this queue's guild
|
|
2173
|
+
* @type {Discord.GuildMember?}
|
|
2174
|
+
*/
|
|
1744
2175
|
get clientMember() {
|
|
1745
2176
|
return this.voice.channel.guild.members.me ?? void 0;
|
|
1746
2177
|
}
|
|
2178
|
+
/**
|
|
2179
|
+
* The filter manager of the queue
|
|
2180
|
+
* @type {FilterManager}
|
|
2181
|
+
* @readonly
|
|
2182
|
+
*/
|
|
1747
2183
|
get filters() {
|
|
1748
2184
|
return __privateGet(this, _filters);
|
|
1749
2185
|
}
|
|
2186
|
+
/**
|
|
2187
|
+
* Formatted duration string.
|
|
2188
|
+
* @type {string}
|
|
2189
|
+
* @readonly
|
|
2190
|
+
*/
|
|
1750
2191
|
get formattedDuration() {
|
|
1751
2192
|
return formatDuration(this.duration);
|
|
1752
2193
|
}
|
|
2194
|
+
/**
|
|
2195
|
+
* Queue's duration.
|
|
2196
|
+
* @type {number}
|
|
2197
|
+
* @readonly
|
|
2198
|
+
*/
|
|
1753
2199
|
get duration() {
|
|
1754
2200
|
return this.songs.length ? this.songs.reduce((prev, next) => prev + next.duration, 0) : 0;
|
|
1755
2201
|
}
|
|
2202
|
+
/**
|
|
2203
|
+
* What time in the song is playing (in seconds).
|
|
2204
|
+
* @type {number}
|
|
2205
|
+
* @readonly
|
|
2206
|
+
*/
|
|
1756
2207
|
get currentTime() {
|
|
1757
2208
|
return this.voice.playbackDuration + this.beginTime;
|
|
1758
2209
|
}
|
|
2210
|
+
/**
|
|
2211
|
+
* Formatted {@link Queue#currentTime} string.
|
|
2212
|
+
* @type {string}
|
|
2213
|
+
* @readonly
|
|
2214
|
+
*/
|
|
1759
2215
|
get formattedCurrentTime() {
|
|
1760
2216
|
return formatDuration(this.currentTime);
|
|
1761
2217
|
}
|
|
2218
|
+
/**
|
|
2219
|
+
* The voice channel playing in.
|
|
2220
|
+
* @type {Discord.VoiceChannel|Discord.StageChannel|null}
|
|
2221
|
+
* @readonly
|
|
2222
|
+
*/
|
|
1762
2223
|
get voiceChannel() {
|
|
1763
2224
|
return this.clientMember?.voice?.channel ?? null;
|
|
1764
2225
|
}
|
|
@@ -1768,6 +2229,14 @@ var Queue = class extends DisTubeBase {
|
|
|
1768
2229
|
set volume(value) {
|
|
1769
2230
|
this.voice.volume = value;
|
|
1770
2231
|
}
|
|
2232
|
+
/**
|
|
2233
|
+
* @private
|
|
2234
|
+
* Add a Song or an array of Song to the queue
|
|
2235
|
+
* @param {Song|Song[]} song Song to add
|
|
2236
|
+
* @param {number} [position=0] Position to add, <= 0 to add to the end of the queue
|
|
2237
|
+
* @throws {Error}
|
|
2238
|
+
* @returns {Queue} The guild queue
|
|
2239
|
+
*/
|
|
1771
2240
|
addToQueue(song, position = 0) {
|
|
1772
2241
|
if (!song || Array.isArray(song) && !song.length) {
|
|
1773
2242
|
throw new DisTubeError("INVALID_TYPE", ["Song", "Array<Song>"], song, "song");
|
|
@@ -1791,6 +2260,10 @@ var Queue = class extends DisTubeBase {
|
|
|
1791
2260
|
delete song.formats;
|
|
1792
2261
|
return this;
|
|
1793
2262
|
}
|
|
2263
|
+
/**
|
|
2264
|
+
* Pause the guild stream
|
|
2265
|
+
* @returns {Queue} The guild queue
|
|
2266
|
+
*/
|
|
1794
2267
|
pause() {
|
|
1795
2268
|
if (this.paused)
|
|
1796
2269
|
throw new DisTubeError("PAUSED");
|
|
@@ -1799,6 +2272,10 @@ var Queue = class extends DisTubeBase {
|
|
|
1799
2272
|
this.voice.pause();
|
|
1800
2273
|
return this;
|
|
1801
2274
|
}
|
|
2275
|
+
/**
|
|
2276
|
+
* Resume the guild stream
|
|
2277
|
+
* @returns {Queue} The guild queue
|
|
2278
|
+
*/
|
|
1802
2279
|
resume() {
|
|
1803
2280
|
if (this.playing)
|
|
1804
2281
|
throw new DisTubeError("RESUMED");
|
|
@@ -1807,10 +2284,22 @@ var Queue = class extends DisTubeBase {
|
|
|
1807
2284
|
this.voice.unpause();
|
|
1808
2285
|
return this;
|
|
1809
2286
|
}
|
|
2287
|
+
/**
|
|
2288
|
+
* Set the guild stream's volume
|
|
2289
|
+
* @param {number} percent The percentage of volume you want to set
|
|
2290
|
+
* @returns {Queue} The guild queue
|
|
2291
|
+
*/
|
|
1810
2292
|
setVolume(percent) {
|
|
1811
2293
|
this.volume = percent;
|
|
1812
2294
|
return this;
|
|
1813
2295
|
}
|
|
2296
|
+
/**
|
|
2297
|
+
* Skip the playing song if there is a next song in the queue.
|
|
2298
|
+
* <info>If {@link Queue#autoplay} is `true` and there is no up next song,
|
|
2299
|
+
* DisTube will add and play a related song.</info>
|
|
2300
|
+
* @returns {Promise<Song>} The song will skip to
|
|
2301
|
+
* @throws {Error}
|
|
2302
|
+
*/
|
|
1814
2303
|
async skip() {
|
|
1815
2304
|
await this._taskQueue.queuing();
|
|
1816
2305
|
try {
|
|
@@ -1828,6 +2317,11 @@ var Queue = class extends DisTubeBase {
|
|
|
1828
2317
|
this._taskQueue.resolve();
|
|
1829
2318
|
}
|
|
1830
2319
|
}
|
|
2320
|
+
/**
|
|
2321
|
+
* Play the previous song if exists
|
|
2322
|
+
* @returns {Promise<Song>} The guild queue
|
|
2323
|
+
* @throws {Error}
|
|
2324
|
+
*/
|
|
1831
2325
|
async previous() {
|
|
1832
2326
|
await this._taskQueue.queuing();
|
|
1833
2327
|
try {
|
|
@@ -1844,6 +2338,10 @@ var Queue = class extends DisTubeBase {
|
|
|
1844
2338
|
this._taskQueue.resolve();
|
|
1845
2339
|
}
|
|
1846
2340
|
}
|
|
2341
|
+
/**
|
|
2342
|
+
* Shuffle the queue's songs
|
|
2343
|
+
* @returns {Promise<Queue>} The guild queue
|
|
2344
|
+
*/
|
|
1847
2345
|
async shuffle() {
|
|
1848
2346
|
await this._taskQueue.queuing();
|
|
1849
2347
|
try {
|
|
@@ -1860,6 +2358,14 @@ var Queue = class extends DisTubeBase {
|
|
|
1860
2358
|
this._taskQueue.resolve();
|
|
1861
2359
|
}
|
|
1862
2360
|
}
|
|
2361
|
+
/**
|
|
2362
|
+
* Jump to the song position in the queue.
|
|
2363
|
+
* The next one is 1, 2,...
|
|
2364
|
+
* The previous one is -1, -2,...
|
|
2365
|
+
* @param {number} position The song position to play
|
|
2366
|
+
* @returns {Promise<Song>} The new Song will be played
|
|
2367
|
+
* @throws {Error} if `num` is invalid number
|
|
2368
|
+
*/
|
|
1863
2369
|
async jump(position) {
|
|
1864
2370
|
await this._taskQueue.queuing();
|
|
1865
2371
|
try {
|
|
@@ -1893,6 +2399,12 @@ var Queue = class extends DisTubeBase {
|
|
|
1893
2399
|
this._taskQueue.resolve();
|
|
1894
2400
|
}
|
|
1895
2401
|
}
|
|
2402
|
+
/**
|
|
2403
|
+
* Set the repeat mode of the guild queue.\
|
|
2404
|
+
* Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
|
|
2405
|
+
* @param {RepeatMode?} [mode] The repeat modes (toggle if `undefined`)
|
|
2406
|
+
* @returns {RepeatMode} The new repeat mode
|
|
2407
|
+
*/
|
|
1896
2408
|
setRepeatMode(mode) {
|
|
1897
2409
|
if (mode !== void 0 && !Object.values(RepeatMode).includes(mode)) {
|
|
1898
2410
|
throw new DisTubeError("INVALID_TYPE", ["RepeatMode", "undefined"], mode, "mode");
|
|
@@ -1905,6 +2417,11 @@ var Queue = class extends DisTubeBase {
|
|
|
1905
2417
|
this.repeatMode = mode;
|
|
1906
2418
|
return this.repeatMode;
|
|
1907
2419
|
}
|
|
2420
|
+
/**
|
|
2421
|
+
* Set the playing time to another position
|
|
2422
|
+
* @param {number} time Time in seconds
|
|
2423
|
+
* @returns {Queue} The guild queue
|
|
2424
|
+
*/
|
|
1908
2425
|
seek(time) {
|
|
1909
2426
|
if (typeof time !== "number")
|
|
1910
2427
|
throw new DisTubeError("INVALID_TYPE", "number", time, "time");
|
|
@@ -1914,6 +2431,11 @@ var Queue = class extends DisTubeBase {
|
|
|
1914
2431
|
this.queues.playSong(this);
|
|
1915
2432
|
return this;
|
|
1916
2433
|
}
|
|
2434
|
+
/**
|
|
2435
|
+
* Add a related song of the playing song to the queue
|
|
2436
|
+
* @returns {Promise<Song>} The added song
|
|
2437
|
+
* @throws {Error}
|
|
2438
|
+
*/
|
|
1917
2439
|
async addRelatedSong() {
|
|
1918
2440
|
if (!this.songs?.[0])
|
|
1919
2441
|
throw new DisTubeError("NO_PLAYING");
|
|
@@ -1926,6 +2448,9 @@ var Queue = class extends DisTubeBase {
|
|
|
1926
2448
|
this.addToQueue(song);
|
|
1927
2449
|
return song;
|
|
1928
2450
|
}
|
|
2451
|
+
/**
|
|
2452
|
+
* Stop the guild stream and delete the queue
|
|
2453
|
+
*/
|
|
1929
2454
|
async stop() {
|
|
1930
2455
|
await this._taskQueue.queuing();
|
|
1931
2456
|
try {
|
|
@@ -1941,6 +2466,11 @@ var Queue = class extends DisTubeBase {
|
|
|
1941
2466
|
this._taskQueue.resolve();
|
|
1942
2467
|
}
|
|
1943
2468
|
}
|
|
2469
|
+
/**
|
|
2470
|
+
* Remove the queue from the manager
|
|
2471
|
+
* (This does not leave the voice channel even if {@link DisTubeOptions|DisTubeOptions.leaveOnStop} is enabled)
|
|
2472
|
+
* @private
|
|
2473
|
+
*/
|
|
1944
2474
|
remove() {
|
|
1945
2475
|
this.stopped = true;
|
|
1946
2476
|
this.songs = [];
|
|
@@ -1953,72 +2483,138 @@ var Queue = class extends DisTubeBase {
|
|
|
1953
2483
|
this.queues.remove(this.id);
|
|
1954
2484
|
this.emit("deleteQueue", this);
|
|
1955
2485
|
}
|
|
2486
|
+
/**
|
|
2487
|
+
* Toggle autoplay mode
|
|
2488
|
+
* @returns {boolean} Autoplay mode state
|
|
2489
|
+
*/
|
|
1956
2490
|
toggleAutoplay() {
|
|
1957
2491
|
this.autoplay = !this.autoplay;
|
|
1958
2492
|
return this.autoplay;
|
|
1959
2493
|
}
|
|
1960
2494
|
};
|
|
1961
|
-
__name(Queue, "Queue");
|
|
1962
2495
|
_filters = new WeakMap();
|
|
2496
|
+
__name(_Queue, "Queue");
|
|
2497
|
+
var Queue = _Queue;
|
|
1963
2498
|
|
|
1964
2499
|
// src/struct/Plugin.ts
|
|
1965
|
-
var
|
|
2500
|
+
var _Plugin = class _Plugin {
|
|
1966
2501
|
constructor() {
|
|
1967
2502
|
__publicField(this, "distube");
|
|
1968
2503
|
}
|
|
1969
2504
|
init(distube) {
|
|
1970
2505
|
this.distube = distube;
|
|
1971
2506
|
}
|
|
2507
|
+
/**
|
|
2508
|
+
* Type of the plugin
|
|
2509
|
+
* @name Plugin#type
|
|
2510
|
+
* @type {PluginType}
|
|
2511
|
+
*/
|
|
2512
|
+
/**
|
|
2513
|
+
* Emit an event to the {@link DisTube} class
|
|
2514
|
+
* @param {string} eventName Event name
|
|
2515
|
+
* @param {...any} args arguments
|
|
2516
|
+
* @returns {boolean}
|
|
2517
|
+
*/
|
|
1972
2518
|
emit(eventName, ...args) {
|
|
1973
2519
|
return this.distube.emit(eventName, ...args);
|
|
1974
2520
|
}
|
|
2521
|
+
/**
|
|
2522
|
+
* Emit error event to the {@link DisTube} class
|
|
2523
|
+
* @param {Error} error error
|
|
2524
|
+
* @param {Discord.BaseGuildTextChannel} [channel] Text channel where the error is encountered.
|
|
2525
|
+
*/
|
|
1975
2526
|
emitError(error, channel) {
|
|
1976
2527
|
this.distube.emitError(error, channel);
|
|
1977
2528
|
}
|
|
2529
|
+
/**
|
|
2530
|
+
* The queue manager
|
|
2531
|
+
* @type {QueueManager}
|
|
2532
|
+
* @readonly
|
|
2533
|
+
*/
|
|
1978
2534
|
get queues() {
|
|
1979
2535
|
return this.distube.queues;
|
|
1980
2536
|
}
|
|
2537
|
+
/**
|
|
2538
|
+
* The voice manager
|
|
2539
|
+
* @type {DisTubeVoiceManager}
|
|
2540
|
+
* @readonly
|
|
2541
|
+
*/
|
|
1981
2542
|
get voices() {
|
|
1982
2543
|
return this.distube.voices;
|
|
1983
2544
|
}
|
|
2545
|
+
/**
|
|
2546
|
+
* Discord.js client
|
|
2547
|
+
* @type {Discord.Client}
|
|
2548
|
+
* @readonly
|
|
2549
|
+
*/
|
|
1984
2550
|
get client() {
|
|
1985
2551
|
return this.distube.client;
|
|
1986
2552
|
}
|
|
2553
|
+
/**
|
|
2554
|
+
* DisTube options
|
|
2555
|
+
* @type {DisTubeOptions}
|
|
2556
|
+
* @readonly
|
|
2557
|
+
*/
|
|
1987
2558
|
get options() {
|
|
1988
2559
|
return this.distube.options;
|
|
1989
2560
|
}
|
|
2561
|
+
/**
|
|
2562
|
+
* DisTube handler
|
|
2563
|
+
* @type {DisTubeHandler}
|
|
2564
|
+
* @readonly
|
|
2565
|
+
*/
|
|
1990
2566
|
get handler() {
|
|
1991
2567
|
return this.distube.handler;
|
|
1992
2568
|
}
|
|
2569
|
+
/**
|
|
2570
|
+
* Check if the string is working with this plugin
|
|
2571
|
+
* @param {string} _string Input string
|
|
2572
|
+
* @returns {boolean|Promise<boolean>}
|
|
2573
|
+
*/
|
|
1993
2574
|
validate(_string) {
|
|
1994
2575
|
return false;
|
|
1995
2576
|
}
|
|
2577
|
+
/**
|
|
2578
|
+
* Get the stream url from {@link Song#url}. Returns {@link Song#url} by default.
|
|
2579
|
+
* Not needed if the plugin plays song from YouTube.
|
|
2580
|
+
* @param {string} url Input url
|
|
2581
|
+
* @returns {string|Promise<string>}
|
|
2582
|
+
*/
|
|
1996
2583
|
getStreamURL(url) {
|
|
1997
2584
|
return url;
|
|
1998
2585
|
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Get related songs from a supported url. {@link Song#member} should be `undefined`.
|
|
2588
|
+
* Not needed to add {@link Song#related} because it will be added with this function later.
|
|
2589
|
+
* @param {string} _url Input url
|
|
2590
|
+
* @returns {Song[]|Promise<Song[]>}
|
|
2591
|
+
*/
|
|
1999
2592
|
getRelatedSongs(_url) {
|
|
2000
2593
|
return [];
|
|
2001
2594
|
}
|
|
2002
2595
|
};
|
|
2003
|
-
__name(
|
|
2596
|
+
__name(_Plugin, "Plugin");
|
|
2597
|
+
var Plugin = _Plugin;
|
|
2004
2598
|
|
|
2005
2599
|
// src/struct/CustomPlugin.ts
|
|
2006
|
-
var
|
|
2600
|
+
var _CustomPlugin = class _CustomPlugin extends Plugin {
|
|
2007
2601
|
constructor() {
|
|
2008
2602
|
super(...arguments);
|
|
2009
2603
|
__publicField(this, "type", "custom" /* CUSTOM */);
|
|
2010
2604
|
}
|
|
2011
2605
|
};
|
|
2012
|
-
__name(
|
|
2606
|
+
__name(_CustomPlugin, "CustomPlugin");
|
|
2607
|
+
var CustomPlugin = _CustomPlugin;
|
|
2013
2608
|
|
|
2014
2609
|
// src/struct/ExtractorPlugin.ts
|
|
2015
|
-
var
|
|
2610
|
+
var _ExtractorPlugin = class _ExtractorPlugin extends Plugin {
|
|
2016
2611
|
constructor() {
|
|
2017
2612
|
super(...arguments);
|
|
2018
2613
|
__publicField(this, "type", "extractor" /* EXTRACTOR */);
|
|
2019
2614
|
}
|
|
2020
2615
|
};
|
|
2021
|
-
__name(
|
|
2616
|
+
__name(_ExtractorPlugin, "ExtractorPlugin");
|
|
2617
|
+
var ExtractorPlugin = _ExtractorPlugin;
|
|
2022
2618
|
|
|
2023
2619
|
// src/util.ts
|
|
2024
2620
|
var import_url = require("url");
|
|
@@ -2027,7 +2623,7 @@ var formatInt = /* @__PURE__ */ __name((int) => int < 10 ? `0${int}` : int, "for
|
|
|
2027
2623
|
function formatDuration(sec) {
|
|
2028
2624
|
if (!sec || !Number(sec))
|
|
2029
2625
|
return "00:00";
|
|
2030
|
-
const seconds = Math.
|
|
2626
|
+
const seconds = Math.floor(sec % 60);
|
|
2031
2627
|
const minutes = Math.floor(sec % 3600 / 60);
|
|
2032
2628
|
const hours = Math.floor(sec / 3600);
|
|
2033
2629
|
if (hours > 0)
|
|
@@ -2062,12 +2658,13 @@ function parseNumber(input) {
|
|
|
2062
2658
|
return Number(input) || 0;
|
|
2063
2659
|
}
|
|
2064
2660
|
__name(parseNumber, "parseNumber");
|
|
2661
|
+
var SUPPORTED_PROTOCOL = ["https:", "http:", "file:"];
|
|
2065
2662
|
function isURL(input) {
|
|
2066
2663
|
if (typeof input !== "string" || input.includes(" "))
|
|
2067
2664
|
return false;
|
|
2068
2665
|
try {
|
|
2069
2666
|
const url = new import_url.URL(input);
|
|
2070
|
-
if (!
|
|
2667
|
+
if (!SUPPORTED_PROTOCOL.some((p) => p === url.protocol))
|
|
2071
2668
|
return false;
|
|
2072
2669
|
} catch {
|
|
2073
2670
|
return false;
|
|
@@ -2106,7 +2703,7 @@ function isMemberInstance(member) {
|
|
|
2106
2703
|
}
|
|
2107
2704
|
__name(isMemberInstance, "isMemberInstance");
|
|
2108
2705
|
function isTextChannelInstance(channel) {
|
|
2109
|
-
return !!channel && isSnowflake(channel.id) && isSnowflake(channel.guildId) && typeof channel.name === "string" && import_discord3.Constants.TextBasedChannelTypes.includes(channel.type) &&
|
|
2706
|
+
return !!channel && isSnowflake(channel.id) && isSnowflake(channel.guildId) && typeof channel.name === "string" && import_discord3.Constants.TextBasedChannelTypes.includes(channel.type) && "messages" in channel && typeof channel.send === "function";
|
|
2110
2707
|
}
|
|
2111
2708
|
__name(isTextChannelInstance, "isTextChannelInstance");
|
|
2112
2709
|
function isMessageInstance(message) {
|
|
@@ -2166,20 +2763,30 @@ function objectKeys(obj) {
|
|
|
2166
2763
|
return Object.keys(obj);
|
|
2167
2764
|
}
|
|
2168
2765
|
__name(objectKeys, "objectKeys");
|
|
2766
|
+
function isNsfwChannel(channel) {
|
|
2767
|
+
if (!isTextChannelInstance(channel))
|
|
2768
|
+
return false;
|
|
2769
|
+
if (channel.isThread())
|
|
2770
|
+
return channel.parent?.nsfw ?? false;
|
|
2771
|
+
return channel.nsfw;
|
|
2772
|
+
}
|
|
2773
|
+
__name(isNsfwChannel, "isNsfwChannel");
|
|
2169
2774
|
|
|
2170
2775
|
// src/plugin/DirectLink.ts
|
|
2171
2776
|
var import_undici = require("undici");
|
|
2172
|
-
var
|
|
2777
|
+
var _DirectLinkPlugin = class _DirectLinkPlugin extends ExtractorPlugin {
|
|
2173
2778
|
async validate(url) {
|
|
2174
2779
|
try {
|
|
2175
2780
|
const headers = await (0, import_undici.request)(url, { method: "HEAD" }).then((res) => res.headers);
|
|
2176
|
-
const
|
|
2177
|
-
|
|
2781
|
+
const types = headers["content-type"];
|
|
2782
|
+
const type = Array.isArray(types) ? types[0] : types;
|
|
2783
|
+
if (["audio/", "video/", "application/ogg"].some((s) => type?.startsWith(s)))
|
|
2178
2784
|
return true;
|
|
2179
2785
|
} catch {
|
|
2180
2786
|
}
|
|
2181
2787
|
return false;
|
|
2182
2788
|
}
|
|
2789
|
+
// eslint-disable-next-line @typescript-eslint/require-await
|
|
2183
2790
|
async resolve(url, options = {}) {
|
|
2184
2791
|
url = url.replace(/\/+$/, "");
|
|
2185
2792
|
return new Song(
|
|
@@ -2192,13 +2799,28 @@ var DirectLinkPlugin = class extends ExtractorPlugin {
|
|
|
2192
2799
|
);
|
|
2193
2800
|
}
|
|
2194
2801
|
};
|
|
2195
|
-
__name(
|
|
2802
|
+
__name(_DirectLinkPlugin, "DirectLinkPlugin");
|
|
2803
|
+
var DirectLinkPlugin = _DirectLinkPlugin;
|
|
2196
2804
|
|
|
2197
2805
|
// src/DisTube.ts
|
|
2198
2806
|
var import_ytsr = __toESM(require("@distube/ytsr"));
|
|
2199
2807
|
var import_tiny_typed_emitter2 = require("tiny-typed-emitter");
|
|
2200
2808
|
var { version } = require_package();
|
|
2201
|
-
var
|
|
2809
|
+
var _DisTube = class _DisTube extends import_tiny_typed_emitter2.TypedEmitter {
|
|
2810
|
+
/**
|
|
2811
|
+
* Create a new DisTube class.
|
|
2812
|
+
* @param {Discord.Client} client Discord.JS client
|
|
2813
|
+
* @param {DisTubeOptions} [otp] Custom DisTube options
|
|
2814
|
+
* @throws {DisTubeError}
|
|
2815
|
+
* @example
|
|
2816
|
+
* const Discord = require('discord.js'),
|
|
2817
|
+
* DisTube = require('distube'),
|
|
2818
|
+
* client = new Discord.Client();
|
|
2819
|
+
* // Create a new DisTube
|
|
2820
|
+
* const distube = new DisTube.default(client, { searchSongs: 10 });
|
|
2821
|
+
* // client.DisTube = distube // make it access easily
|
|
2822
|
+
* client.login("Your Discord Bot Token")
|
|
2823
|
+
*/
|
|
2202
2824
|
constructor(client, otp = {}) {
|
|
2203
2825
|
super();
|
|
2204
2826
|
__publicField(this, "handler");
|
|
@@ -2228,9 +2850,36 @@ var DisTube = class extends import_tiny_typed_emitter2.TypedEmitter {
|
|
|
2228
2850
|
static get version() {
|
|
2229
2851
|
return version;
|
|
2230
2852
|
}
|
|
2853
|
+
/**
|
|
2854
|
+
* DisTube version
|
|
2855
|
+
* @type {string}
|
|
2856
|
+
*/
|
|
2231
2857
|
get version() {
|
|
2232
2858
|
return version;
|
|
2233
2859
|
}
|
|
2860
|
+
/**
|
|
2861
|
+
* Play / add a song or playlist from url. Search and play a song if it is not a valid url.
|
|
2862
|
+
*
|
|
2863
|
+
* @param {Discord.BaseGuildVoiceChannel} voiceChannel The channel will be joined if the bot isn't in any channels,
|
|
2864
|
+
* the bot will be moved to this channel if {@link DisTubeOptions}.joinNewVoiceChannel is `true`
|
|
2865
|
+
* @param {string|Song|SearchResult|Playlist} song URL | Search string |
|
|
2866
|
+
* {@link Song} | {@link SearchResult} | {@link Playlist}
|
|
2867
|
+
* @param {PlayOptions} [options] Optional options
|
|
2868
|
+
* @throws {DisTubeError}
|
|
2869
|
+
* @example
|
|
2870
|
+
* client.on('message', (message) => {
|
|
2871
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
2872
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
2873
|
+
* const command = args.shift();
|
|
2874
|
+
* if (command == "play")
|
|
2875
|
+
* distube.play(message.member.voice.channel, args.join(" "), {
|
|
2876
|
+
* member: message.member,
|
|
2877
|
+
* textChannel: message.channel,
|
|
2878
|
+
* message
|
|
2879
|
+
* });
|
|
2880
|
+
* });
|
|
2881
|
+
* @returns {Promise<void>}
|
|
2882
|
+
*/
|
|
2234
2883
|
async play(voiceChannel, song, options = {}) {
|
|
2235
2884
|
if (!isSupportedVoiceChannel(voiceChannel)) {
|
|
2236
2885
|
throw new DisTubeError("INVALID_TYPE", "BaseGuildVoiceChannel", voiceChannel, "voiceChannel");
|
|
@@ -2297,6 +2946,20 @@ ${e.message}`;
|
|
|
2297
2946
|
queue?._taskQueue.resolve();
|
|
2298
2947
|
}
|
|
2299
2948
|
}
|
|
2949
|
+
/**
|
|
2950
|
+
* Create a custom playlist
|
|
2951
|
+
* @returns {Promise<Playlist>}
|
|
2952
|
+
* @param {Array<string|Song|SearchResult>} songs Array of url, Song or SearchResult
|
|
2953
|
+
* @param {CustomPlaylistOptions} [options] Optional options
|
|
2954
|
+
* @example
|
|
2955
|
+
* const songs = ["https://www.youtube.com/watch?v=xxx", "https://www.youtube.com/watch?v=yyy"];
|
|
2956
|
+
* const playlist = await distube.createCustomPlaylist(songs, {
|
|
2957
|
+
* member: message.member,
|
|
2958
|
+
* properties: { name: "My playlist name", source: "custom" },
|
|
2959
|
+
* parallel: true
|
|
2960
|
+
* });
|
|
2961
|
+
* distube.play(voiceChannel, playlist, { ... });
|
|
2962
|
+
*/
|
|
2300
2963
|
async createCustomPlaylist(songs, options = {}) {
|
|
2301
2964
|
const { member, properties, parallel, metadata } = { parallel: true, ...options };
|
|
2302
2965
|
if (!Array.isArray(songs))
|
|
@@ -2311,8 +2974,6 @@ ${e.message}`;
|
|
|
2311
2974
|
if (member && !isMemberInstance(member)) {
|
|
2312
2975
|
throw new DisTubeError("INVALID_TYPE", "Discord.Member", member, "options.member");
|
|
2313
2976
|
}
|
|
2314
|
-
if (!filteredSongs.length)
|
|
2315
|
-
throw new DisTubeError("NO_VALID_SONG");
|
|
2316
2977
|
let resolvedSongs;
|
|
2317
2978
|
if (parallel) {
|
|
2318
2979
|
const promises = filteredSongs.map(
|
|
@@ -2328,6 +2989,18 @@ ${e.message}`;
|
|
|
2328
2989
|
}
|
|
2329
2990
|
return new Playlist(resolvedSongs, { member, properties, metadata });
|
|
2330
2991
|
}
|
|
2992
|
+
/**
|
|
2993
|
+
* Search for a song. You can customize how user answers instead of send a number.
|
|
2994
|
+
* Then use {@link DisTube#play} to play it.
|
|
2995
|
+
*
|
|
2996
|
+
* @param {string} string The string search for
|
|
2997
|
+
* @param {Object} options Search options
|
|
2998
|
+
* @param {number} [options.limit=10] Limit the results
|
|
2999
|
+
* @param {SearchResultType} [options.type=SearchResultType.VIDEO] Type of results (`video` or `playlist`).
|
|
3000
|
+
* @param {boolean} [options.safeSearch=false] Whether or not use safe search (YouTube restricted mode)
|
|
3001
|
+
* @throws {Error}
|
|
3002
|
+
* @returns {Promise<Array<SearchResult>>} Array of results
|
|
3003
|
+
*/
|
|
2331
3004
|
async search(string, options = {}) {
|
|
2332
3005
|
const opts = { type: "video" /* VIDEO */, limit: 10, safeSearch: false, ...options };
|
|
2333
3006
|
if (typeof opts.type !== "string" || !["video", "playlist"].includes(opts.type)) {
|
|
@@ -2357,63 +3030,234 @@ ${e.message}`;
|
|
|
2357
3030
|
return this.search(string, options);
|
|
2358
3031
|
}
|
|
2359
3032
|
}
|
|
3033
|
+
/**
|
|
3034
|
+
* Get the guild queue
|
|
3035
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3036
|
+
* @returns {Queue?}
|
|
3037
|
+
* @throws {Error}
|
|
3038
|
+
* @example
|
|
3039
|
+
* client.on('message', (message) => {
|
|
3040
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3041
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3042
|
+
* const command = args.shift();
|
|
3043
|
+
* if (command == "queue") {
|
|
3044
|
+
* const queue = distube.getQueue(message);
|
|
3045
|
+
* message.channel.send('Current queue:\n' + queue.songs.map((song, id) =>
|
|
3046
|
+
* `**${id+1}**. [${song.name}](${song.url}) - \`${song.formattedDuration}\``
|
|
3047
|
+
* ).join("\n"));
|
|
3048
|
+
* }
|
|
3049
|
+
* });
|
|
3050
|
+
*/
|
|
2360
3051
|
getQueue(guild) {
|
|
2361
3052
|
return this.queues.get(guild);
|
|
2362
3053
|
}
|
|
3054
|
+
/**
|
|
3055
|
+
* Pause the guild stream
|
|
3056
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3057
|
+
* @returns {Queue} The guild queue
|
|
3058
|
+
* @throws {Error}
|
|
3059
|
+
*/
|
|
2363
3060
|
pause(guild) {
|
|
2364
3061
|
const q = this.getQueue(guild);
|
|
2365
3062
|
if (!q)
|
|
2366
3063
|
throw new DisTubeError("NO_QUEUE");
|
|
2367
3064
|
return q.pause();
|
|
2368
3065
|
}
|
|
3066
|
+
/**
|
|
3067
|
+
* Resume the guild stream
|
|
3068
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3069
|
+
* @returns {Queue} The guild queue
|
|
3070
|
+
* @throws {Error}
|
|
3071
|
+
*/
|
|
2369
3072
|
resume(guild) {
|
|
2370
3073
|
const q = this.getQueue(guild);
|
|
2371
3074
|
if (!q)
|
|
2372
3075
|
throw new DisTubeError("NO_QUEUE");
|
|
2373
3076
|
return q.resume();
|
|
2374
3077
|
}
|
|
3078
|
+
/**
|
|
3079
|
+
* Stop the guild stream
|
|
3080
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3081
|
+
* @returns {Promise<void>}
|
|
3082
|
+
* @throws {Error}
|
|
3083
|
+
* @example
|
|
3084
|
+
* client.on('message', (message) => {
|
|
3085
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3086
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3087
|
+
* const command = args.shift();
|
|
3088
|
+
* if (command == "stop") {
|
|
3089
|
+
* distube.stop(message);
|
|
3090
|
+
* message.channel.send("Stopped the queue!");
|
|
3091
|
+
* }
|
|
3092
|
+
* });
|
|
3093
|
+
*/
|
|
2375
3094
|
stop(guild) {
|
|
2376
3095
|
const q = this.getQueue(guild);
|
|
2377
3096
|
if (!q)
|
|
2378
3097
|
throw new DisTubeError("NO_QUEUE");
|
|
2379
3098
|
return q.stop();
|
|
2380
3099
|
}
|
|
3100
|
+
/**
|
|
3101
|
+
* Set the guild stream's volume
|
|
3102
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3103
|
+
* @param {number} percent The percentage of volume you want to set
|
|
3104
|
+
* @returns {Queue} The guild queue
|
|
3105
|
+
* @throws {Error}
|
|
3106
|
+
* @example
|
|
3107
|
+
* client.on('message', (message) => {
|
|
3108
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3109
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3110
|
+
* const command = args.shift();
|
|
3111
|
+
* if (command == "volume")
|
|
3112
|
+
* distube.setVolume(message, Number(args[0]));
|
|
3113
|
+
* });
|
|
3114
|
+
*/
|
|
2381
3115
|
setVolume(guild, percent) {
|
|
2382
3116
|
const q = this.getQueue(guild);
|
|
2383
3117
|
if (!q)
|
|
2384
3118
|
throw new DisTubeError("NO_QUEUE");
|
|
2385
3119
|
return q.setVolume(percent);
|
|
2386
3120
|
}
|
|
3121
|
+
/**
|
|
3122
|
+
* Skip the playing song if there is a next song in the queue.
|
|
3123
|
+
* <info>If {@link Queue#autoplay} is `true` and there is no up next song,
|
|
3124
|
+
* DisTube will add and play a related song.</info>
|
|
3125
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3126
|
+
* @returns {Promise<Song>} The new Song will be played
|
|
3127
|
+
* @throws {Error}
|
|
3128
|
+
* @example
|
|
3129
|
+
* client.on('message', (message) => {
|
|
3130
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3131
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3132
|
+
* const command = args.shift();
|
|
3133
|
+
* if (command == "skip")
|
|
3134
|
+
* distube.skip(message);
|
|
3135
|
+
* });
|
|
3136
|
+
*/
|
|
2387
3137
|
skip(guild) {
|
|
2388
3138
|
const q = this.getQueue(guild);
|
|
2389
3139
|
if (!q)
|
|
2390
3140
|
throw new DisTubeError("NO_QUEUE");
|
|
2391
3141
|
return q.skip();
|
|
2392
3142
|
}
|
|
3143
|
+
/**
|
|
3144
|
+
* Play the previous song
|
|
3145
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3146
|
+
* @returns {Promise<Song>} The new Song will be played
|
|
3147
|
+
* @throws {Error}
|
|
3148
|
+
* @example
|
|
3149
|
+
* client.on('message', (message) => {
|
|
3150
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3151
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3152
|
+
* const command = args.shift();
|
|
3153
|
+
* if (command == "previous")
|
|
3154
|
+
* distube.previous(message);
|
|
3155
|
+
* });
|
|
3156
|
+
*/
|
|
2393
3157
|
previous(guild) {
|
|
2394
3158
|
const q = this.getQueue(guild);
|
|
2395
3159
|
if (!q)
|
|
2396
3160
|
throw new DisTubeError("NO_QUEUE");
|
|
2397
3161
|
return q.previous();
|
|
2398
3162
|
}
|
|
3163
|
+
/**
|
|
3164
|
+
* Shuffle the guild queue songs
|
|
3165
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3166
|
+
* @returns {Promise<Queue>} The guild queue
|
|
3167
|
+
* @example
|
|
3168
|
+
* client.on('message', (message) => {
|
|
3169
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3170
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3171
|
+
* const command = args.shift();
|
|
3172
|
+
* if (command == "shuffle")
|
|
3173
|
+
* distube.shuffle(message);
|
|
3174
|
+
* });
|
|
3175
|
+
*/
|
|
2399
3176
|
shuffle(guild) {
|
|
2400
3177
|
const q = this.getQueue(guild);
|
|
2401
3178
|
if (!q)
|
|
2402
3179
|
throw new DisTubeError("NO_QUEUE");
|
|
2403
3180
|
return q.shuffle();
|
|
2404
3181
|
}
|
|
3182
|
+
/**
|
|
3183
|
+
* Jump to the song number in the queue.
|
|
3184
|
+
* The next one is 1, 2,...
|
|
3185
|
+
* The previous one is -1, -2,...
|
|
3186
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3187
|
+
* @param {number} num The song number to play
|
|
3188
|
+
* @returns {Promise<Song>} The new Song will be played
|
|
3189
|
+
* @throws {Error} if `num` is invalid number (0 < num < {@link Queue#songs}.length)
|
|
3190
|
+
* @example
|
|
3191
|
+
* client.on('message', (message) => {
|
|
3192
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3193
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3194
|
+
* const command = args.shift();
|
|
3195
|
+
* if (command == "jump")
|
|
3196
|
+
* distube.jump(message, parseInt(args[0]))
|
|
3197
|
+
* .catch(err => message.channel.send("Invalid song number."));
|
|
3198
|
+
* });
|
|
3199
|
+
*/
|
|
2405
3200
|
jump(guild, num) {
|
|
2406
3201
|
const q = this.getQueue(guild);
|
|
2407
3202
|
if (!q)
|
|
2408
3203
|
throw new DisTubeError("NO_QUEUE");
|
|
2409
3204
|
return q.jump(num);
|
|
2410
3205
|
}
|
|
3206
|
+
/**
|
|
3207
|
+
* Set the repeat mode of the guild queue.\
|
|
3208
|
+
* Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
|
|
3209
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3210
|
+
* @param {RepeatMode?} [mode] The repeat modes (toggle if `undefined`)
|
|
3211
|
+
* @returns {RepeatMode} The new repeat mode
|
|
3212
|
+
* @example
|
|
3213
|
+
* client.on('message', (message) => {
|
|
3214
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3215
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3216
|
+
* const command = args.shift();
|
|
3217
|
+
* if (command == "repeat") {
|
|
3218
|
+
* let mode = distube.setRepeatMode(message, parseInt(args[0]));
|
|
3219
|
+
* mode = mode ? mode == 2 ? "Repeat queue" : "Repeat song" : "Off";
|
|
3220
|
+
* message.channel.send("Set repeat mode to `" + mode + "`");
|
|
3221
|
+
* }
|
|
3222
|
+
* });
|
|
3223
|
+
* @example
|
|
3224
|
+
* const { RepeatMode } = require("distube");
|
|
3225
|
+
* let mode;
|
|
3226
|
+
* switch(distube.setRepeatMode(message, parseInt(args[0]))) {
|
|
3227
|
+
* case RepeatMode.DISABLED:
|
|
3228
|
+
* mode = "Off";
|
|
3229
|
+
* break;
|
|
3230
|
+
* case RepeatMode.SONG:
|
|
3231
|
+
* mode = "Repeat a song";
|
|
3232
|
+
* break;
|
|
3233
|
+
* case RepeatMode.QUEUE:
|
|
3234
|
+
* mode = "Repeat all queue";
|
|
3235
|
+
* break;
|
|
3236
|
+
* }
|
|
3237
|
+
* message.channel.send("Set repeat mode to `" + mode + "`");
|
|
3238
|
+
*/
|
|
2411
3239
|
setRepeatMode(guild, mode) {
|
|
2412
3240
|
const q = this.getQueue(guild);
|
|
2413
3241
|
if (!q)
|
|
2414
3242
|
throw new DisTubeError("NO_QUEUE");
|
|
2415
3243
|
return q.setRepeatMode(mode);
|
|
2416
3244
|
}
|
|
3245
|
+
/**
|
|
3246
|
+
* Toggle autoplay mode
|
|
3247
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3248
|
+
* @returns {boolean} Autoplay mode state
|
|
3249
|
+
* @throws {Error}
|
|
3250
|
+
* @example
|
|
3251
|
+
* client.on('message', (message) => {
|
|
3252
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3253
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3254
|
+
* const command = args.shift();
|
|
3255
|
+
* if (command == "autoplay") {
|
|
3256
|
+
* const mode = distube.toggleAutoplay(message);
|
|
3257
|
+
* message.channel.send("Set autoplay mode to `" + (mode ? "On" : "Off") + "`");
|
|
3258
|
+
* }
|
|
3259
|
+
* });
|
|
3260
|
+
*/
|
|
2417
3261
|
toggleAutoplay(guild) {
|
|
2418
3262
|
const q = this.getQueue(guild);
|
|
2419
3263
|
if (!q)
|
|
@@ -2421,18 +3265,43 @@ ${e.message}`;
|
|
|
2421
3265
|
q.autoplay = !q.autoplay;
|
|
2422
3266
|
return q.autoplay;
|
|
2423
3267
|
}
|
|
3268
|
+
/**
|
|
3269
|
+
* Add related song to the queue
|
|
3270
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3271
|
+
* @returns {Promise<Song>} The guild queue
|
|
3272
|
+
*/
|
|
2424
3273
|
addRelatedSong(guild) {
|
|
2425
3274
|
const q = this.getQueue(guild);
|
|
2426
3275
|
if (!q)
|
|
2427
3276
|
throw new DisTubeError("NO_QUEUE");
|
|
2428
3277
|
return q.addRelatedSong();
|
|
2429
3278
|
}
|
|
3279
|
+
/**
|
|
3280
|
+
* Set the playing time to another position
|
|
3281
|
+
* @param {GuildIdResolvable} guild The type can be resolved to give a {@link Queue}
|
|
3282
|
+
* @param {number} time Time in seconds
|
|
3283
|
+
* @returns {Queue} Seeked queue
|
|
3284
|
+
* @example
|
|
3285
|
+
* client.on('message', message => {
|
|
3286
|
+
* if (!message.content.startsWith(config.prefix)) return;
|
|
3287
|
+
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
|
|
3288
|
+
* const command = args.shift();
|
|
3289
|
+
* if (command = 'seek')
|
|
3290
|
+
* distube.seek(message, Number(args[0]));
|
|
3291
|
+
* });
|
|
3292
|
+
*/
|
|
2430
3293
|
seek(guild, time) {
|
|
2431
3294
|
const q = this.getQueue(guild);
|
|
2432
3295
|
if (!q)
|
|
2433
3296
|
throw new DisTubeError("NO_QUEUE");
|
|
2434
3297
|
return q.seek(time);
|
|
2435
3298
|
}
|
|
3299
|
+
/**
|
|
3300
|
+
* Emit error event
|
|
3301
|
+
* @param {Error} error error
|
|
3302
|
+
* @param {Discord.BaseGuildTextChannel} [channel] Text channel where the error is encountered.
|
|
3303
|
+
* @private
|
|
3304
|
+
*/
|
|
2436
3305
|
emitError(error, channel) {
|
|
2437
3306
|
if (this.listeners("error").length) {
|
|
2438
3307
|
this.emit("error", channel, error);
|
|
@@ -2445,7 +3314,8 @@ ${e.message}`;
|
|
|
2445
3314
|
}
|
|
2446
3315
|
}
|
|
2447
3316
|
};
|
|
2448
|
-
__name(
|
|
3317
|
+
__name(_DisTube, "DisTube");
|
|
3318
|
+
var DisTube = _DisTube;
|
|
2449
3319
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2450
3320
|
0 && (module.exports = {
|
|
2451
3321
|
BaseManager,
|
|
@@ -2485,6 +3355,7 @@ __name(DisTube, "DisTube");
|
|
|
2485
3355
|
isGuildInstance,
|
|
2486
3356
|
isMemberInstance,
|
|
2487
3357
|
isMessageInstance,
|
|
3358
|
+
isNsfwChannel,
|
|
2488
3359
|
isObject,
|
|
2489
3360
|
isRecord,
|
|
2490
3361
|
isSnowflake,
|