disrexom
Version:
A Discord.js module to simplify your music commands and play songs with audio filters on Discord without any API key. Support YouTube, SoundCloud, Bandcamp, Facebook, and 700+ more sites
480 lines • 15.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Queue = void 0;
const core_1 = require("../core");
const __1 = require("..");
/**
* Represents a queue.
* @extends DisTubeBase
*/
class Queue extends core_1.DisTubeBase {
/**
* Create a queue for the guild
* @param {DisTube} distube DisTube
* @param {DisTubeVoice} voice Voice connection
* @param {Song|Song[]} song First song(s)
* @param {Discord.TextChannel?} textChannel Default text channel
*/
constructor(distube, voice, song, textChannel) {
var _a;
super(distube);
/**
* The client user as a `GuildMember` of this queue's guild
* @type {Discord.GuildMember}
*/
this.clientMember = (_a = voice.channel.guild) === null || _a === void 0 ? void 0 : _a.me;
/**
* Voice connection of this queue.
* @type {DisTubeVoice}
*/
this.voice = voice;
/**
* Queue id (Guild id)
* @type {Discord.Snowflake}
*/
this.id = voice.id;
/**
* Get or set the stream volume. Default value: `50`.
* @type {number}
*/
this.volume = 50;
/**
* List of songs in the queue (The first one is the playing song)
* @type {Array<Song>}
*/
this.songs = Array.isArray(song) ? [...song] : [song];
/**
* List of the previous songs.
* @type {Array<Song>}
*/
this.previousSongs = [];
/**
* Whether stream is currently stopped.
* @type {boolean}
* @private
*/
this.stopped = false;
/**
* Whether or not the last song was skipped to next song.
* @type {boolean}
* @private
*/
this.next = false;
/**
* Whether or not the last song was skipped to previous song.
* @type {boolean}
* @private
*/
this.prev = false;
/**
* Whether or not the stream is currently playing.
* @type {boolean}
*/
this.playing = true;
/**
* Whether or not the stream is currently paused.
* @type {boolean}
*/
this.paused = false;
/**
* Type of repeat mode (`0` is disabled, `1` is repeating a song, `2` is repeating all the queue).
* Default value: `0` (disabled)
* @type {RepeatMode}
*/
this.repeatMode = __1.RepeatMode.DISABLED;
/**
* Whether or not the autoplay mode is enabled.
* Default value: `false`
* @type {boolean}
*/
this.autoplay = false;
/**
* Enabled audio filters.
* Available filters: {@link Filters}
* @type {Array<string>}
*/
this.filters = [];
/**
* What time in the song to begin (in seconds).
* @type {number}
*/
this.beginTime = 0;
/**
* The text channel of the Queue. (Default: where the first command is called).
* @type {Discord.TextChannel?}
*/
this.textChannel = textChannel;
/**
* Timeout for checking empty channel
* @type {*}
* @private
*/
this.emptyTimeout = undefined;
/**
* Task queuing system
* @type {TaskQueue}
* @private
*/
this.taskQueue = new __1.TaskQueue();
/**
* DisTubeVoice listener
* @type {Object}
* @private
*/
this.listeners = undefined;
}
/**
* Formatted duration string.
* @type {string}
*/
get formattedDuration() {
return (0, __1.formatDuration)(this.duration);
}
/**
* Queue's duration.
* @type {number}
*/
get duration() {
return this.songs.length ? this.songs.reduce((prev, next) => prev + next.duration, 0) : 0;
}
/**
* What time in the song is playing (in seconds).
* @type {number}
*/
get currentTime() {
return this.voice.playbackDuration + this.beginTime;
}
/**
* Formatted {@link Queue#currentTime} string.
* @type {string}
*/
get formattedCurrentTime() {
return (0, __1.formatDuration)(this.currentTime);
}
/**
* The voice channel playing in.
* @type {Discord.VoiceChannel|Discord.StageChannel|null}
*/
get voiceChannel() {
return this.clientMember.voice.channel;
}
get volume() {
return this.voice.volume;
}
set volume(value) {
this.voice.volume = value;
}
/**
* Add a Song or an array of Song to the queue
* @param {Song|Song[]} song Song to add
* @param {number} [position=-1] Position to add, < 0 to add to the end of the queue
* @param {boolean} [queuing=true] Wether or not waiting for unfinished tasks
* @throws {Error}
* @returns {Queue} The guild queue
*/
addToQueue(song, position = -1) {
const isArray = Array.isArray(song);
if (!song || (isArray && !song.length)) {
throw new __1.DisTubeError("INVALID_TYPE", ["Song", "SearchResult", "Array<Song|SearchResult>"], song, "song");
}
if (position === 0)
throw new __1.DisTubeError("ADD_BEFORE_PLAYING");
if (position < 0) {
if (isArray)
this.songs.push(...song);
else
this.songs.push(song);
}
else if (isArray) {
this.songs.splice(position, 0, ...song);
}
else {
this.songs.splice(position, 0, song);
}
if (isArray)
song.map(s => delete s.formats);
else
delete song.formats;
return this;
}
/**
* Pause the guild stream
* @returns {Queue} The guild queue
*/
pause() {
if (this.paused)
throw new __1.DisTubeError("PAUSED");
this.playing = false;
this.paused = true;
this.voice.pause();
return this;
}
/**
* Resume the guild stream
* @returns {Queue} The guild queue
*/
resume() {
if (this.playing)
throw new __1.DisTubeError("RESUMED");
this.playing = true;
this.paused = false;
this.voice.unpause();
return this;
}
/**
* Set the guild stream's volume
* @param {number} percent The percentage of volume you want to set
* @returns {Queue} The guild queue
*/
setVolume(percent) {
this.volume = percent;
return this;
}
/**
* Skip the playing song if there is a next song in the queue.
* <info>If {@link Queue#autoplay} is `true` and there is no up next song,
* DisTube will add and play a related song.</info>
* @returns {Promise<Song>} The song will skip to
* @throws {Error}
*/
async skip() {
await this.taskQueue.queuing();
try {
if (this.songs.length <= 1) {
if (this.autoplay)
await this.addRelatedSong();
else
throw new __1.DisTubeError("NO_UP_NEXT");
}
const song = this.songs[1];
this.next = true;
this.voice.stop();
return song;
}
finally {
this.taskQueue.resolve();
}
}
/**
* Play the previous song if exists
* @returns {Song} The guild queue
* @throws {Error}
*/
async previous() {
var _a;
await this.taskQueue.queuing();
try {
if (!this.options.savePreviousSongs)
throw new __1.DisTubeError("DISABLED_OPTION", "savePreviousSongs");
if (((_a = this.previousSongs) === null || _a === void 0 ? void 0 : _a.length) === 0 && this.repeatMode !== __1.RepeatMode.QUEUE) {
throw new __1.DisTubeError("NO_PREVIOUS");
}
const song = this.repeatMode === 2 ? this.songs[this.songs.length - 1] : this.previousSongs[this.previousSongs.length - 1];
this.prev = true;
this.voice.stop();
return song;
}
finally {
this.taskQueue.resolve();
}
}
/**
* Shuffle the queue's songs
* @returns {Promise<Queue>} The guild queue
*/
async shuffle() {
await this.taskQueue.queuing();
try {
if (!this.songs.length)
return this;
const playing = this.songs.shift();
for (let i = this.songs.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[this.songs[i], this.songs[j]] = [this.songs[j], this.songs[i]];
}
this.songs.unshift(playing);
return this;
}
finally {
this.taskQueue.resolve();
}
}
/**
* Jump to the song position in the queue.
* The next one is 1, 2,...
* The previous one is -1, -2,...
* @param {number} position The song position to play
* @returns {Promise<Queue>} The guild queue
* @throws {Error} if `num` is invalid number
*/
async jump(position) {
await this.taskQueue.queuing();
try {
if (typeof position !== "number")
throw new __1.DisTubeError("INVALID_TYPE", "number", position, "position");
if (!position || position > this.songs.length || -position > this.previousSongs.length) {
throw new __1.DisTubeError("NO_SONG_POSITION");
}
if (position > 0) {
const nextSongs = this.songs.splice(position - 1);
if (this.options.savePreviousSongs) {
this.previousSongs.push(...this.songs);
}
else {
this.previousSongs.push(...this.songs.map(s => {
return { id: s.id };
}));
}
this.songs = nextSongs;
this.next = true;
}
else if (!this.options.savePreviousSongs) {
throw new __1.DisTubeError("DISABLED_OPTION", "savePreviousSongs");
}
else {
this.prev = true;
if (position !== -1)
this.songs.unshift(...this.previousSongs.splice(position + 1));
}
this.voice.stop();
return this;
}
finally {
this.taskQueue.resolve();
}
}
/**
* Set the repeat mode of the guild queue.\
* Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
* @param {RepeatMode?} [mode] The repeat modes (toggle if `undefined`)
* @returns {RepeatMode} The new repeat mode
*/
setRepeatMode(mode) {
if (mode !== undefined && !Object.values(__1.RepeatMode).includes(mode)) {
throw new __1.DisTubeError("INVALID_TYPE", ["RepeatMode", "undefined"], mode, "mode");
}
if (mode === undefined)
this.repeatMode = (this.repeatMode + 1) % 3;
else if (this.repeatMode === mode)
this.repeatMode = __1.RepeatMode.DISABLED;
else
this.repeatMode = mode;
return this.repeatMode;
}
/**
* Enable or disable filter(s) of the queue.
* Available filters: {@link Filters}
* @param {string|string[]|false} filter A filter name, an array of filter name or `false` to clear all the filters
* @param {boolean} [force=false] Force enable the input filter(s) even if it's enabled
* @returns {Array<string>} Enabled filters.
* @throws {Error}
*/
setFilter(filter, force = false) {
if (Array.isArray(filter)) {
filter = filter.filter(f => Object.prototype.hasOwnProperty.call(this.distube.filters, f));
if (!filter.length)
throw new __1.DisTubeError("EMPTY_FILTERED_ARRAY", "filter", "filter name");
for (const f of filter) {
if (this.filters.includes(f)) {
if (!force)
this.filters.splice(this.filters.indexOf(f), 1);
}
else {
this.filters.push(f);
}
}
}
else if (filter === false) {
this.filters = [];
}
else if (!Object.prototype.hasOwnProperty.call(this.distube.filters, filter)) {
throw new __1.DisTubeError("INVALID_TYPE", "filter name", filter, "filter");
}
else if (this.filters.includes(filter)) {
if (!force)
this.filters.splice(this.filters.indexOf(filter), 1);
}
else {
this.filters.push(filter);
}
this.beginTime = this.currentTime;
this.queues.playSong(this);
return this.filters;
}
/**
* Set the playing time to another position
* @param {number} time Time in seconds
* @returns {Queue} The guild queue
*/
seek(time) {
if (typeof time !== "number")
throw new __1.DisTubeError("INVALID_TYPE", "number", time, "time");
if (isNaN(time) || time < 0)
throw new __1.DisTubeError("NUMBER_COMPARE", "time", "bigger or equal to", 0);
this.beginTime = time;
this.queues.playSong(this);
return this;
}
/**
* Add a related song of the playing song to the queue
* @returns {Promise<Song>} The added song
* @throws {Error}
*/
async addRelatedSong() {
var _a;
if (!((_a = this.songs) === null || _a === void 0 ? void 0 : _a[0]))
throw new __1.DisTubeError("NO_PLAYING");
const related = this.songs[0].related.find(v => !this.previousSongs.map(s => s.id).includes(v.id));
if (!related || !(related instanceof __1.Song))
throw new __1.DisTubeError("NO_RELATED");
const song = await this.handler.resolveSong(this.clientMember, related.url);
if (!(song instanceof __1.Song))
throw new __1.DisTubeError("CANNOT_PLAY_RELATED");
this.addToQueue(song);
return song;
}
/**
* Stop the guild stream
*/
async stop() {
await this.taskQueue.queuing();
try {
this.stopped = true;
this.voice.stop();
if (this.options.leaveOnStop)
this.voice.leave();
this.delete();
}
finally {
this.taskQueue.resolve();
}
}
/**
* Delete the queue from the manager
* (This does not leave the queue even if {@link DisTubeOptions|DisTubeOptions.leaveOnStop} is enabled)
* @private
*/
delete() {
this.stopped = true;
this.songs = [];
this.previousSongs = [];
if (this.listeners) {
for (const event of Object.keys(this.listeners)) {
this.voice.removeListener(event, this.listeners[event]);
}
}
this.queues.delete(this.id);
this.emit("deleteQueue", this);
}
/**
* Toggle autoplay mode
* @returns {boolean} Autoplay mode state
*/
toggleAutoplay() {
this.autoplay = !this.autoplay;
return this.autoplay;
}
}
exports.Queue = Queue;
exports.default = Queue;
//# sourceMappingURL=Queue.js.map