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
778 lines • 34.6 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function(mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DisTube = void 0;
const ytpl_1 = __importDefault(require("@distube/ytpl"));
const ytsr_1 = __importDefault(require("@distube/ytsr"));
const util_1 = require("./util");
const tiny_typed_emitter_1 = require("tiny-typed-emitter");
const _1 = require(".");
/**
* DisTube class
* @extends EventEmitter
*/
class DisTube extends tiny_typed_emitter_1.TypedEmitter {
/**
* Create a new DisTube class.
* @param {Discord.Client} client Discord.JS client
* @param {DisTubeOptions} [otp] Custom DisTube options
* @example
* const Discord = require('discord.js'),
* DisTube = require('distube'),
* client = new Discord.Client();
* // Create a new DisTube
* const distube = new DisTube.default(client, { searchSongs: 10 });
* // client.DisTube = distube // make it access easily
* client.login("Your Discord Bot Token")
*/
constructor(client, otp = {}) {
super();
this.setMaxListeners(1);
if (!(0, _1.isClientInstance)(client))
throw new _1.DisTubeError("INVALID_TYPE", "Discord.Client", client, "client");
/**
* Discord.JS client
* @type {Discord.Client}
*/
this.client = client;
(0, util_1.checkIntents)(client.options);
/**
* DisTube options
* @type {DisTubeOptions}
*/
this.options = new _1.Options(otp);
/**
* Voice connections manager
* @type {DisTubeVoiceManager}
*/
this.voices = new _1.DisTubeVoiceManager(this);
/**
* DisTube's Handler
* @type {DisTubeHandler}
* @private
*/
this.handler = new _1.DisTubeHandler(this);
/**
* Queues manager
* @type {QueueManager}
*/
this.queues = new _1.QueueManager(this);
/**
* DisTube filters
* @type {Filters}
*/
this.filters = _1.defaultFilters;
Object.assign(this.filters, this.options.customFilters);
// Default plugin
this.options.plugins.push(new _1.HTTPPlugin(), new _1.HTTPSPlugin());
if (this.options.youtubeDL)
this.options.plugins.push(new _1.YouTubeDLPlugin(this.options.updateYouTubeDL));
this.options.plugins.map(p => p.init(this));
/**
* Extractor Plugins
* @type {ExtractorPlugin[]}
* @private
*/
this.extractorPlugins = this.options.plugins.filter((p) => p.type === "extractor");
/**
* Custom Plugins
* @type {CustomPlugin[]}
* @private
*/
this.customPlugins = this.options.plugins.filter((p) => p.type === "custom");
}
/**
* Shorthand method for {@link DisTube#playVoiceChannel}
* @returns {Promise<void>}
* @param {Discord.Message} message A message from guild channel
* @param {string|Song|SearchResult|Playlist} song URL | Search string |
* {@link Song} | {@link SearchResult} | {@link Playlist}
* @param {Object} [options] Optional options
* @param {boolean} [options.skip=false] Skip the playing song (if exists) and play the added song/playlist instantly
* @param {boolean} [options.unshift=false] Add the song/playlist to the beginning of the queue
* (after the playing song if exists)
* @example
* client.on('message', (message) => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if (command == "play")
* distube.play(message, args.join(" "));
* });
*/
async play(message, song, options = {}) {
if (!song)
return;
if (typeof options !== "object" || Array.isArray(options)) {
throw new _1.DisTubeError("INVALID_TYPE", "object", options, "options");
}
const textChannel = message.channel;
const { skip, unshift } = Object.assign({ skip: false, unshift: false }, options);
const member = message.member;
const voiceChannel = member.voice.channel;
if (!voiceChannel)
throw new _1.DisTubeError("NOT_IN_VOICE");
await this.playVoiceChannel(voiceChannel, song, {
member,
textChannel,
skip,
message,
unshift,
});
}
/**
* Play / add a song or playlist from url. Search and play a song if it is not a valid url.
* Emit {@link DisTube#addList}, {@link DisTube#addSong} or {@link DisTube#playSong} after executing
* @returns {Promise<void>}
* @param {Discord.VoiceChannel|Discord.StageChannel} voiceChannel The voice channel will be joined
* @param {string|Song|SearchResult|Playlist} song URL | Search string |
* {@link Song} | {@link SearchResult} | {@link Playlist}
* @param {Object} [options] Optional options
* @param {boolean} [options.skip=false] Skip the playing song (if exists) and play the added song/playlist instantly
* @param {boolean} [options.unshift=false] Add the song/playlist to the beginning of the queue
* (after the playing song if exists)
* @param {Discord.GuildMember} [options.member] Requested user (default is your bot)
* @param {Discord.TextChannel} [options.textChannel] Default {@link Queue#textChannel} (if the queue wasn't created)
* @param {Discord.Message} [options.message] Called message (For built-in search events. If this is a {@link https://developer.mozilla.org/en-US/docs/Glossary/Falsy|falsy value}, it will play the first result instead)
*/
async playVoiceChannel(voiceChannel, song, options = {}) {
var _a;
if (!(0, _1.isSupportedVoiceChannel)(voiceChannel))
throw new _1.DisTubeError("NOT_SUPPORTED_VOICE");
if (typeof options !== "object" || Array.isArray(options)) {
throw new _1.DisTubeError("INVALID_TYPE", "object", options, "options");
}
const { textChannel, member, skip, message, unshift } = Object.assign({
member: voiceChannel.guild.me,
skip: false,
unshift: false,
}, options);
try {
if (typeof song === "string") {
for (const plugin of this.customPlugins) {
if (await plugin.validate(song)) {
return plugin.play(voiceChannel, song, member, textChannel, skip, unshift);
}
}
}
let queue = this.getQueue(voiceChannel);
const queuing = queue && !queue.taskQueue.hasResolveTask;
if (queuing)
await (queue === null || queue === void 0 ? void 0 : queue.taskQueue.queuing(true));
try {
if (song instanceof _1.SearchResult && song.type === "playlist")
song = song.url;
if (typeof song === "string" && ytpl_1.default.validateID(song))
song = await this.handler.resolvePlaylist(member, song);
if (typeof song === "string" && !(0, util_1.isURL)(song)) {
if (!message)
song = (await this.search(song, { limit: 1 }))[0];
else
song = await this.handler.searchSong(message, song);
}
song = await this.handler.resolveSong(member, song);
if (!song)
return;
if (song instanceof _1.Playlist) {
await this.handler.handlePlaylist(voiceChannel, song, textChannel, skip, unshift);
} else if (!this.options.nsfw && song.age_restricted && !(textChannel === null || textChannel === void 0 ? void 0 : textChannel.nsfw)) {
throw new _1.DisTubeError("NON_NSFW");
} else {
queue = this.getQueue(voiceChannel);
if (queue) {
queue.addToQueue(song, skip || unshift ? 1 : -1);
if (skip)
queue.skip();
else
this.emit("addSong", queue, song);
} else {
const newQueue = await this.handler.createQueue(voiceChannel, song, textChannel);
if (newQueue instanceof _1.Queue) {
if (this.options.emitAddSongWhenCreatingQueue)
this.emit("addSong", newQueue, song);
this.emit("playSong", newQueue, song);
}
}
}
} finally {
if (queuing)
queue === null || queue === void 0 ? void 0 : queue.taskQueue.resolve();
}
} catch (e) {
if (!(e instanceof _1.DisTubeError)) {
try {
e.name = "PlayError";
e.message = `${((_a = song) === null || _a === void 0 ? void 0 : _a.url) || song}\n${e.message}`;
} catch {}
}
this.emitError(e, textChannel);
}
}
/**
* <info>Shorthand method of {@link DisTubeHandler#createCustomPlaylist} and {@link DisTube#playVoiceChannel}
*
* If you doesn't have a user message (interaction,...),
* see {@link DisTubeHandler#createCustomPlaylist} example</info>
*
* Play or add array of video urls.
* {@link DisTube#event:playSong} or {@link DisTube#event:addList} will be emitted
* with `playlist`'s properties include `properties` parameter's properties such as
* `user`, `songs`, `duration`, `formattedDuration`, `thumbnail` like {@link Playlist}
* @returns {Promise<void>}
* @param {Discord.Message} message A message from guild channel
* @param {Array<string|Song|SearchResult>} songs Array of url, Song or SearchResult
* @param {Object} [properties={}] Additional properties such as `name`
* @param {Object} [options] Optional options
* @param {boolean} [options.skip=false] Skip the playing song (if exists) and play the added song/playlist instantly
* @param {boolean} [options.unshift=false] Add the song/playlist to the beginning of the queue
* (after the playing song if exists)
* @param {boolean} [options.parallel=true] Whether or not fetch the songs in parallel
* @example
* const songs = ["https://www.youtube.com/watch?v=xxx", "https://www.youtube.com/watch?v=yyy"];
* distube.playCustomPlaylist(message, songs, { name: "My playlist name" });
* // Fetching custom playlist sequentially (reduce lag for low specs)
* distube.playCustomPlaylist(message, songs, { name: "My playlist name" }, false, false);
*/
async playCustomPlaylist(message, songs, properties = {}, options = {}) {
try {
if (typeof options !== "object" || Array.isArray(options)) {
throw new _1.DisTubeError("INVALID_TYPE", "object", options, "options");
}
const { skip, unshift, parallel } = Object.assign({
skip: false,
unshift: false,
parallel: true,
}, options);
const queue = this.getQueue(message);
const queuing = queue && !queue.taskQueue.hasResolveTask;
if (queuing)
await (queue === null || queue === void 0 ? void 0 : queue.taskQueue.queuing(true));
try {
const playlist = await this.handler.createCustomPlaylist(message, songs, properties, parallel);
await this.handler.handlePlaylist(message, playlist, message.channel, skip, unshift);
} finally {
if (queuing)
queue === null || queue === void 0 ? void 0 : queue.taskQueue.resolve();
}
} catch (e) {
this.emitError(e, message.channel);
}
}
/**
* Search for a song.
* You can customize how user answers instead of send a number.
* Then use {@link DisTube#play} or {@link DisTube#playVoiceChannel} to play it.
* @param {string} string The string search for
* @param {Object} options Search options
* @param {number} [options.limit=10] Limit the results
* @param {'video'|'playlist'} [options.type='video'] Type of results (`video` or `playlist`).
* @param {boolean} [options.safeSearch=false] Whether or not use safe search (YouTube restricted mode)
* @throws {Error}
* @returns {Promise<Array<SearchResult>>} Array of results
*/
async search(string, options = {}) {
const opts = Object.assign({ type: "video", limit: 10, safeSearch: false }, options);
if (typeof opts.type !== "string" || !["video", "playlist"].includes(opts.type)) {
throw new _1.DisTubeError("INVALID_TYPE", ["video", "playlist"], opts.type, "options.type");
}
if (typeof opts.limit !== "number")
throw new _1.DisTubeError("INVALID_TYPE", "number", opts.limit, "options.limit");
if (opts.limit < 1)
throw new _1.DisTubeError("NUMBER_COMPARE", "option.limit", "bigger or equal to", 1);
if (typeof opts.safeSearch !== "boolean") {
throw new _1.DisTubeError("INVALID_TYPE", "boolean", opts.safeSearch, "options.safeSearch");
}
try {
const search = await (0, ytsr_1.default)(string, opts);
const results = search.items.map(i => new _1.SearchResult(i));
if (results.length === 0)
throw new _1.DisTubeError("NO_RESULT");
return results;
} catch (e) {
if (options.retried)
throw e;
options.retried = true;
return this.search(string, options);
}
}
/**
* Get the guild queue
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @returns {Queue?}
* @throws {Error}
* @example
* client.on('message', (message) => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if (command == "queue") {
* const queue = distube.getQueue(message);
* message.channel.send('Current queue:\n' + queue.songs.map((song, id) =>
* `**${id+1}**. [${song.name}](${song.url}) - \`${song.formattedDuration}\``
* ).join("\n"));
* }
* });
*/
getQueue(queue) {
return this.queues.get(queue);
}
/**
* Pause the guild stream
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @returns {Queue} The guild queue
* @throws {Error}
*/
pause(queue) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.pause();
}
/**
* Resume the guild stream
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @returns {Queue} The guild queue
* @throws {Error}
*/
resume(queue) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.resume();
}
/**
* Stop the guild stream
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @returns {Promise<void>}
* @throws {Error}
* @example
* client.on('message', (message) => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if (command == "stop") {
* distube.stop(message);
* message.channel.send("Stopped the queue!");
* }
* });
*/
stop(queue) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.stop();
}
/**
* Set the guild stream's volume
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @param {number} percent The percentage of volume you want to set
* @returns {Queue} The guild queue
* @throws {Error}
* @example
* client.on('message', (message) => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if (command == "volume")
* distube.setVolume(message, Number(args[0]));
* });
*/
setVolume(queue, percent) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.setVolume(percent);
}
/**
* 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>
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @returns {Promise<Song>} The new Song will be played
* @throws {Error}
* @example
* client.on('message', (message) => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if (command == "skip")
* distube.skip(message);
* });
*/
skip(queue) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.skip();
}
/**
* Play the previous song
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @returns {Promise<Song>} The new Song will be played
* @throws {Error}
* @example
* client.on('message', (message) => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if (command == "previous")
* distube.previous(message);
* });
*/
previous(queue) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.previous();
}
/**
* Shuffle the guild queue songs
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @returns {Promise<Queue>} The guild queue
* @example
* client.on('message', (message) => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if (command == "shuffle")
* distube.shuffle(message);
* });
*/
shuffle(queue) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.shuffle();
}
/**
* Jump to the song number in the queue.
* The next one is 1, 2,...
* The previous one is -1, -2,...
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @param {number} num The song number to play
* @returns {Promise<Queue>} The guild queue
* @throws {Error} if `num` is invalid number (0 < num < {@link Queue#songs}.length)
* @example
* client.on('message', (message) => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if (command == "jump")
* distube.jump(message, parseInt(args[0]))
* .catch(err => message.channel.send("Invalid song number."));
* });
*/
jump(queue, num) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.jump(num);
}
/**
* Set the repeat mode of the guild queue.\
* Toggle mode `(Disabled -> Song -> Queue -> Disabled ->...)` if `mode` is `undefined`
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @param {RepeatMode?} [mode] The repeat modes (toggle if `undefined`)
* @returns {RepeatMode} The new repeat mode
* @example
* client.on('message', (message) => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if (command == "repeat") {
* let mode = distube.setRepeatMode(message, parseInt(args[0]));
* mode = mode ? mode == 2 ? "Repeat queue" : "Repeat song" : "Off";
* message.channel.send("Set repeat mode to `" + mode + "`");
* }
* });
* @example
* const { RepeatMode } = require("distube");
* let mode;
* switch(distube.setRepeatMode(message, parseInt(args[0]))) {
* case RepeatMode.DISABLED:
* mode = "Off";
* break;
* case RepeatMode.SONG:
* mode = "Repeat a song";
* break;
* case RepeatMode.QUEUE:
* mode = "Repeat all queue";
* break;
* }
* message.channel.send("Set repeat mode to `" + mode + "`");
*/
setRepeatMode(queue, mode) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.setRepeatMode(mode);
}
/**
* Toggle autoplay mode
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @returns {boolean} Autoplay mode state
* @throws {Error}
* @example
* client.on('message', (message) => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if (command == "autoplay") {
* const mode = distube.toggleAutoplay(message);
* message.channel.send("Set autoplay mode to `" + (mode ? "On" : "Off") + "`");
* }
* });
*/
toggleAutoplay(queue) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
q.autoplay = !q.autoplay;
return q.autoplay;
}
/**
* Add related song to the queue
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @returns {Promise<Song>} The guild queue
*/
addRelatedSong(queue) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.addRelatedSong();
}
/**
* Enable or disable filter(s) of the queue.
* Available filters: {@link Filters}
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @param {string|false} filter A filter name, `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.
* @example
* client.on('message', (message) => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if ([`3d`, `bassboost`, `echo`, `karaoke`, `nightcore`, `vaporwave`].includes(command)) {
* const filter = distube.setFilter(message, command);
* message.channel.send("Current queue filter: " + (filter.join(", ") || "Off"));
* }
* });
*/
setFilter(queue, filter, force = false) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.setFilter(filter, force);
}
/**
* Set the playing time to another position
* @param {GuildIDResolvable} queue The type can be resolved to give a {@link Queue}
* @param {number} time Time in seconds
* @returns {Queue} Seeked queue
* @example
* client.on('message', message => {
* if (!message.content.startsWith(config.prefix)) return;
* const args = message.content.slice(config.prefix.length).trim().split(/ +/g);
* const command = args.shift();
* if (command = 'seek')
* distube.seek(message, Number(args[0]));
* });
*/
seek(queue, time) {
const q = this.getQueue(queue);
if (!q)
throw new _1.DisTubeError("NO_QUEUE");
return q.seek(time);
}
/* eslint-disable no-console */
/**
* Emit error event
* @param {Error} error error
* @param {Discord.TextChannel?} channel Text channel where the error is encountered.
* @private
*/
emitError(error, channel) {
if (!channel || !(0, _1.isTextChannelInstance)(channel)) {
console.error(error);
console.warn("This is logged because <Queue>.textChannel is undefined");
} else if (this.listeners("error").length) {
this.emit("error", channel, error);
} else {
console.error(error);
console.warn("Unhandled 'error' event.");
console.warn("See: https://distube.js.org/#/docs/DisTube/stable/class/DisTube?scrollTo=e-error and https://nodejs.org/api/events.html#events_error_events");
}
}
}
exports.DisTube = DisTube;
exports.default = DisTube;
/**
* Emitted after DisTube add a new playlist to the playing {@link Queue}.
*
* @event DisTube#addList
* @param {Queue} queue The guild queue
* @param {Playlist} playlist Playlist info
* @example
* distube.on("addList", (queue, playlist) => queue.textChannel.send(
* `Added \`${playlist.name}\` playlist (${playlist.songs.length} songs) to the queue!`
* ));
*/
/**
* Emitted after DisTube add a new song to the playing {@link Queue}.
*
* @event DisTube#addSong
* @param {Queue} queue The guild queue
* @param {Song} song Added song
* @example
* distube.on("addSong", (queue, song) => queue.textChannel.send(
* `Added ${song.name} - \`${song.formattedDuration}\` to the queue by ${song.user}.`
* ));
*/
/**
* Emitted when there is no user in the voice channel,
* {@link DisTubeOptions}.leaveOnEmpty is `true` and there is a playing queue.
*
* If there is no playing queue (stopped and {@link DisTubeOptions}.leaveOnStop is `false`),
* it will leave the channel without emitting this event.
* @event DisTube#empty
* @param {Queue} queue The guild queue
* @example
* distube.on("empty", queue => queue.textChannel.send("Channel is empty. Leaving the channel"))
*/
/**
* Emitted when DisTube encounters an error.
*
* @event DisTube#error
* @param {Discord.TextChannel} channel Text channel where the error is encountered.
* @param {Error} error The error encountered
* @example
* distube.on("error", (channel, error) => channel.send(
* "An error encountered: " + error
* ));
*/
/**
* Emitted when there is no more song in the queue and {@link Queue#autoplay} is `false`.
* DisTube will leave voice channel if {@link DisTubeOptions}.leaveOnFinish is `true`.
*
* @event DisTube#finish
* @param {Queue} queue The guild queue
* @example
* distube.on("finish", queue => queue.textChannel.send("No more song in queue"));
*/
/**
* Emitted when DisTube initialize a queue to change queue default properties.
*
* @event DisTube#initQueue
* @param {Queue} queue The guild queue
* @example
* distube.on("initQueue", queue => {
* queue.autoplay = false;
* queue.volume = 100;
* });
*/
/**
* Emitted when {@link Queue#autoplay} is `true`, {@link Queue#songs} is empty,
* and DisTube cannot find related songs to play.
*
* @event DisTube#noRelated
* @param {Queue} queue The guild queue
* @example
* distube.on("noRelated", queue => queue.textChannel.send("Can't find related video to play."));
*/
/**
* Emitted when DisTube play a song.
*
* If {@link DisTubeOptions}.emitNewSongOnly is `true`,
* this event is not emitted when looping a song or next song is the previous one.
*
* @event DisTube#playSong
* @param {Queue} queue The guild queue
* @param {Song} song Playing song
* @example
* distube.on("playSong", (queue, song) => queue.textChannel.send(
* `Playing \`${song.name}\` - \`${song.formattedDuration}\`\nRequested by: ${song.user}`
* ));
*/
/**
* Emitted when DisTube cannot find any results for the query.
*
* @event DisTube#searchNoResult
* @param {Discord.Message} message The user message called play method
* @param {string} query The search query
* @example
* distube.on("searchNoResult", (message, query) => message.channel.send(`No result found for ${query}!`));
*/
/**
* Emitted when {@link DisTubeOptions|DisTubeOptions.searchSongs} bigger than 0,
* and song param of {@link DisTube#playVoiceChannel} is invalid url.
* DisTube will wait for user's next message to choose a song manually.
* <info>{@link https://support.google.com/youtube/answer/7354993|Safe search} is enabled
* if {@link DisTubeOptions}.nsfw is disabled and the message's channel is not a nsfw channel.</info>
*
* @event DisTube#searchResult
* @param {Discord.Message} message The user message called play method
* @param {Array<SearchResult>} results Searched results
* @param {string} query The search query
* @example
* // DisTubeOptions.searchSongs > 0
* distube.on("searchResult", (message, results) => {
* message.channel.send(`**Choose an option from below**\n${
* results.map((song, i) => `**${i + 1}**. ${song.name} - \`${song.formattedDuration}\``).join("\n")
* }\n*Enter anything else or wait 60 seconds to cancel*`);
* });
*/
/**
* Emitted when {@link DisTubeOptions|DisTubeOptions.searchSongs} bigger than 0,
* and the search canceled due to {@link DisTubeOptions|DisTubeOptions.searchTimeout}.
*
* @event DisTube#searchCancel
* @param {Discord.Message} message The user message called play method
* @param {string} query The search query
* @example
* // DisTubeOptions.searchSongs > 0
* distube.on("searchCancel", (message) => message.channel.send(`Searching canceled`));
*/
/**
* Emitted when {@link DisTubeOptions|DisTubeOptions.searchSongs} bigger than 0,
* and the search canceled due to user's next message is not a number or out of results range.
*
* @event DisTube#searchInvalidAnswer
* @param {Discord.Message} message The user message called play method
* @param {Discord.Message} answer The answered message of user
* @param {string} query The search query
* @example
* // DisTubeOptions.searchSongs > 0
* distube.on("searchInvalidAnswer", (message) => message.channel.send(`You answered an invalid number!`));
*/
/**
* Emitted when {@link DisTubeOptions|DisTubeOptions.searchSongs} bigger than 0,
* and after the user chose a search result to play.
*
* @event DisTube#searchDone
* @param {Discord.Message} message The user message called play method
* @param {Discord.Message} answer The answered message of user
* @param {string} query The search query
*/
/**
* Emitted when the bot is disconnected to a voice channel.
*
* @event DisTube#disconnect
* @param {Queue} queue The guild queue
*/
/**
* Emitted when a {@link Queue} is deleted with any reasons.
*
* @event DisTube#deleteQueue
* @param {Queue} queue The guild queue
*/
/**
* Emitted when DisTube finished a song.
*
* @event DisTube#finishSong
* @param {Queue} queue The guild queue
* @param {Song} song Finished song
*/
//# sourceMappingURL=DisTube.js.map