2021-06-14 22:33:32 +05:00
|
|
|
import { Collection, Guild, StageChannel, VoiceChannel } from "discord.js";
|
2021-06-11 15:32:22 +05:00
|
|
|
import { Player } from "../Player";
|
2021-06-11 23:19:52 +05:00
|
|
|
import { StreamDispatcher } from "../VoiceInterface/BasicStreamDispatcher";
|
2021-06-11 15:32:22 +05:00
|
|
|
import Track from "./Track";
|
2021-06-19 23:10:38 +05:00
|
|
|
import { PlayerOptions, PlayOptions, QueueFilters, QueueRepeatMode } from "../types/types";
|
2021-06-11 23:19:52 +05:00
|
|
|
import ytdl from "discord-ytdl-core";
|
|
|
|
import { AudioResource, StreamType } from "@discordjs/voice";
|
2021-06-13 17:43:36 +05:00
|
|
|
import { Util } from "../utils/Util";
|
2021-06-13 18:09:25 +05:00
|
|
|
import YouTube from "youtube-sr";
|
2021-06-19 23:10:38 +05:00
|
|
|
import AudioFilters from "../utils/AudioFilters";
|
2021-06-11 15:32:22 +05:00
|
|
|
|
2021-06-12 11:37:41 +05:00
|
|
|
class Queue<T = unknown> {
|
2021-06-11 15:32:22 +05:00
|
|
|
public readonly guild: Guild;
|
|
|
|
public readonly player: Player;
|
2021-06-11 23:19:52 +05:00
|
|
|
public connection: StreamDispatcher;
|
2021-06-11 15:32:22 +05:00
|
|
|
public tracks: Track[] = [];
|
2021-06-13 17:43:36 +05:00
|
|
|
public previousTracks: Track[] = [];
|
2021-06-11 15:32:22 +05:00
|
|
|
public options: PlayerOptions;
|
2021-06-11 23:19:52 +05:00
|
|
|
public playing = false;
|
2021-06-12 11:37:41 +05:00
|
|
|
public metadata?: T = null;
|
2021-06-13 17:43:36 +05:00
|
|
|
public repeatMode: QueueRepeatMode = 0;
|
2021-06-20 10:37:59 +05:00
|
|
|
private _streamTime: number = 0;
|
2021-06-13 21:47:04 +05:00
|
|
|
public _cooldownsTimeout = new Collection<string, NodeJS.Timeout>();
|
2021-06-20 10:37:59 +05:00
|
|
|
private _activeFilters: any[] = [];
|
2021-06-11 15:32:22 +05:00
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Queue constructor
|
|
|
|
* @param {Player} player The player that instantiated this queue
|
|
|
|
* @param {Guild} guild The guild that instantiated this queue
|
|
|
|
* @param {PlayerOptions={}} options Player options for the queue
|
|
|
|
*/
|
2021-06-11 15:32:22 +05:00
|
|
|
constructor(player: Player, guild: Guild, options: PlayerOptions = {}) {
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* The player that instantiated this queue
|
|
|
|
* @type {Player}
|
|
|
|
*/
|
2021-06-11 15:32:22 +05:00
|
|
|
this.player = player;
|
2021-06-14 22:51:54 +05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The guild that instantiated this queue
|
|
|
|
* @type {Guild}
|
|
|
|
*/
|
2021-06-11 15:32:22 +05:00
|
|
|
this.guild = guild;
|
2021-06-14 22:51:54 +05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The player options for this queue
|
|
|
|
* @type {PlayerOptions}
|
|
|
|
*/
|
2021-06-11 15:32:22 +05:00
|
|
|
this.options = {};
|
|
|
|
|
|
|
|
Object.assign(
|
|
|
|
this.options,
|
|
|
|
{
|
|
|
|
leaveOnEnd: true,
|
|
|
|
leaveOnStop: true,
|
|
|
|
leaveOnEmpty: true,
|
|
|
|
leaveOnEmptyCooldown: 1000,
|
|
|
|
autoSelfDeaf: true,
|
|
|
|
enableLive: false,
|
2021-06-14 19:32:37 +05:00
|
|
|
ytdlOptions: {},
|
2021-06-11 15:32:22 +05:00
|
|
|
useSafeSearch: false,
|
|
|
|
disableAutoRegister: false,
|
2021-06-11 23:19:52 +05:00
|
|
|
fetchBeforeQueued: false,
|
2021-06-20 10:37:59 +05:00
|
|
|
initialVolume: 100
|
2021-06-11 15:32:22 +05:00
|
|
|
} as PlayerOptions,
|
|
|
|
options
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Returns current track
|
|
|
|
* @returns {Track}
|
|
|
|
*/
|
2021-06-11 16:50:43 +05:00
|
|
|
get current() {
|
2021-06-11 23:19:52 +05:00
|
|
|
return this.connection.audioResource?.metadata ?? this.tracks[0];
|
2021-06-11 16:50:43 +05:00
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Returns current track
|
|
|
|
* @returns {Track}
|
|
|
|
*/
|
2021-06-13 21:47:04 +05:00
|
|
|
nowPlaying() {
|
|
|
|
return this.current;
|
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Connects to a voice channel
|
|
|
|
* @param {StageChannel|VoiceChannel} channel
|
|
|
|
* @returns {Promise<Queue>}
|
|
|
|
*/
|
2021-06-11 23:19:52 +05:00
|
|
|
async connect(channel: StageChannel | VoiceChannel) {
|
2021-06-13 13:06:19 +05:00
|
|
|
if (!["stage", "voice"].includes(channel?.type)) throw new TypeError(`Channel type must be voice or stage, got ${channel?.type}!`);
|
2021-06-14 19:44:15 +05:00
|
|
|
const connection = await this.player.voiceUtils.connect(channel, {
|
|
|
|
deaf: this.options.autoSelfDeaf
|
|
|
|
});
|
2021-06-11 23:19:52 +05:00
|
|
|
this.connection = connection;
|
2021-06-11 15:32:22 +05:00
|
|
|
|
2021-06-13 14:22:45 +05:00
|
|
|
// it's ok to use this here since Queue listens to the events 1 time per play and destroys the listener
|
2021-06-13 14:20:05 +05:00
|
|
|
this.connection.setMaxListeners(Infinity);
|
|
|
|
|
2021-06-13 10:55:20 +05:00
|
|
|
if (channel.type === "stage") await channel.guild.me.voice.setRequestToSpeak(true).catch(() => {});
|
2021-06-12 11:37:41 +05:00
|
|
|
|
2021-06-13 17:03:20 +05:00
|
|
|
this.connection.on("error", (err) => this.player.emit("error", this, err));
|
|
|
|
this.connection.on("debug", (msg) => this.player.emit("debug", this, msg));
|
2021-06-13 14:22:45 +05:00
|
|
|
|
2021-06-14 00:34:59 +05:00
|
|
|
this.player.emit("connectionCreate", this, this.connection);
|
2021-06-14 11:45:03 +05:00
|
|
|
|
2021-06-11 15:32:22 +05:00
|
|
|
return this;
|
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Destroys this queue
|
|
|
|
*/
|
2021-06-18 00:09:02 +05:00
|
|
|
destroy(disconnect = this.options.leaveOnStop) {
|
2021-06-11 23:19:52 +05:00
|
|
|
this.connection.end();
|
2021-06-18 00:09:02 +05:00
|
|
|
if (disconnect) this.connection.disconnect();
|
2021-06-11 15:32:22 +05:00
|
|
|
this.player.queues.delete(this.guild.id);
|
|
|
|
}
|
2021-06-11 16:50:43 +05:00
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Skips current track
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2021-06-11 23:19:52 +05:00
|
|
|
skip() {
|
|
|
|
if (!this.connection) return false;
|
2021-06-12 00:18:53 +05:00
|
|
|
this.connection.end();
|
|
|
|
return true;
|
2021-06-11 23:19:52 +05:00
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Adds single track to the queue
|
|
|
|
* @param {Track} track The track to add
|
|
|
|
* @returns {void}
|
|
|
|
*/
|
2021-06-11 23:19:52 +05:00
|
|
|
addTrack(track: Track) {
|
2021-06-13 13:17:50 +05:00
|
|
|
this.tracks.push(track);
|
2021-06-13 13:06:19 +05:00
|
|
|
this.player.emit("trackAdd", this, track);
|
2021-06-11 23:19:52 +05:00
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Adds multiple tracks to the queue
|
|
|
|
* @param {Track[]} tracks Array of tracks to add
|
|
|
|
*/
|
2021-06-11 23:19:52 +05:00
|
|
|
addTracks(tracks: Track[]) {
|
|
|
|
this.tracks.push(...tracks);
|
2021-06-13 13:17:50 +05:00
|
|
|
this.player.emit("tracksAdd", this, tracks);
|
2021-06-11 23:19:52 +05:00
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Sets paused state
|
|
|
|
* @param {boolean} paused The paused state
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2021-06-12 00:18:53 +05:00
|
|
|
setPaused(paused?: boolean) {
|
|
|
|
if (!this.connection) return false;
|
2021-06-19 19:12:13 +05:00
|
|
|
return paused ? this.connection.pause(true) : this.connection.resume();
|
2021-06-12 00:18:53 +05:00
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Sets bitrate
|
|
|
|
* @param {number|"auto"} bitrate bitrate to set
|
|
|
|
*/
|
2021-06-12 11:37:41 +05:00
|
|
|
setBitrate(bitrate: number | "auto") {
|
|
|
|
if (!this.connection?.audioResource?.encoder) return;
|
|
|
|
if (bitrate === "auto") bitrate = this.connection.channel?.bitrate ?? 64000;
|
|
|
|
this.connection.audioResource.encoder.setBitrate(bitrate);
|
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Sets volume
|
|
|
|
* @param {number} amount The volume amount
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2021-06-13 13:06:19 +05:00
|
|
|
setVolume(amount: number) {
|
|
|
|
if (!this.connection) return false;
|
2021-06-13 14:06:01 +05:00
|
|
|
this.options.initialVolume = amount;
|
2021-06-13 13:06:19 +05:00
|
|
|
return this.connection.setVolume(amount);
|
|
|
|
}
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Sets repeat mode
|
|
|
|
* @param {QueueRepeatMode} mode The repeat mode
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
2021-06-13 17:43:36 +05:00
|
|
|
setRepeatMode(mode: QueueRepeatMode) {
|
2021-06-20 12:36:05 +05:00
|
|
|
if (![QueueRepeatMode.OFF, QueueRepeatMode.QUEUE, QueueRepeatMode.TRACK, QueueRepeatMode.AUTOPLAY].includes(mode)) throw new Error(`Unknown repeat mode "${mode}"!`);
|
2021-06-19 19:12:13 +05:00
|
|
|
if (mode === this.repeatMode) return false;
|
2021-06-13 17:43:36 +05:00
|
|
|
this.repeatMode = mode;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Returns current volume amount
|
|
|
|
*/
|
2021-06-13 13:06:19 +05:00
|
|
|
get volume() {
|
|
|
|
if (!this.connection) return 100;
|
|
|
|
return this.connection.volume;
|
|
|
|
}
|
|
|
|
|
2021-06-17 18:22:03 +05:00
|
|
|
/**
|
|
|
|
* Alternative volume setter
|
|
|
|
*/
|
|
|
|
set volume(amount: number) {
|
|
|
|
this.setVolume(amount);
|
|
|
|
}
|
|
|
|
|
2021-06-19 23:10:38 +05:00
|
|
|
get streamTime() {
|
|
|
|
if (!this.connection) return 0;
|
2021-06-20 10:37:59 +05:00
|
|
|
const playbackTime = this._streamTime + this.connection.streamTime;
|
|
|
|
const NC = this._activeFilters.includes("nightcore") ? 1.25 : null;
|
|
|
|
const VW = this._activeFilters.includes("vaporwave") ? 0.8 : null;
|
|
|
|
|
|
|
|
if (NC && VW) return playbackTime * (NC + VW);
|
|
|
|
return NC ? playbackTime * NC : VW ? playbackTime * VW : playbackTime;
|
|
|
|
}
|
|
|
|
|
|
|
|
getFiltersEnabled() {
|
|
|
|
return AudioFilters.names.filter((x) => this._activeFilters.includes(x));
|
2021-06-19 23:10:38 +05:00
|
|
|
}
|
|
|
|
|
2021-06-20 10:37:59 +05:00
|
|
|
getFiltersDisabled() {
|
|
|
|
return AudioFilters.names.filter((x) => !this._activeFilters.includes(x));
|
|
|
|
}
|
|
|
|
|
|
|
|
async setFilters(filters?: QueueFilters) {
|
|
|
|
if (!filters || !Object.keys(filters).length) {
|
|
|
|
// reset filters
|
|
|
|
const streamTime = this.streamTime;
|
|
|
|
this._activeFilters = [];
|
|
|
|
return await this.play(this.current, {
|
|
|
|
immediate: true,
|
|
|
|
filtersUpdate: true,
|
|
|
|
seek: streamTime,
|
|
|
|
encoderArgs: []
|
|
|
|
});
|
|
|
|
}
|
2021-06-19 23:10:38 +05:00
|
|
|
|
|
|
|
const _filters: any[] = [];
|
|
|
|
|
|
|
|
for (const filter in filters) {
|
|
|
|
if (filters[filter as keyof QueueFilters] === true) _filters.push(filter);
|
|
|
|
}
|
|
|
|
|
2021-06-20 10:37:59 +05:00
|
|
|
if (this._activeFilters.join("") === _filters.join("")) return;
|
|
|
|
|
2021-06-19 23:10:38 +05:00
|
|
|
const newFilters = AudioFilters.create(_filters);
|
2021-06-20 10:37:59 +05:00
|
|
|
const streamTime = this.streamTime;
|
|
|
|
this._activeFilters = _filters;
|
2021-06-19 23:10:38 +05:00
|
|
|
|
|
|
|
return await this.play(this.current, {
|
2021-06-20 00:48:48 +05:00
|
|
|
immediate: true,
|
2021-06-19 23:10:38 +05:00
|
|
|
filtersUpdate: true,
|
2021-06-20 10:37:59 +05:00
|
|
|
seek: streamTime,
|
2021-06-19 23:10:38 +05:00
|
|
|
encoderArgs: ["-af", newFilters]
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-06-20 11:57:17 +05:00
|
|
|
async seek(position: number) {
|
|
|
|
if (!this.playing || !this.current) return false;
|
|
|
|
if (position < 1) position = 0;
|
|
|
|
if (position >= this.current.durationMS) return this.skip();
|
2021-06-20 12:36:16 +05:00
|
|
|
|
2021-06-20 11:57:17 +05:00
|
|
|
await this.play(this.current, {
|
|
|
|
immediate: true,
|
|
|
|
filtersUpdate: true, // to stop events
|
|
|
|
seek: position
|
|
|
|
});
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* Plays previous track
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2021-06-13 17:46:15 +05:00
|
|
|
async back() {
|
|
|
|
return await this.play(Util.last(this.previousTracks), { immediate: true });
|
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* @param {Track} [src] The track to play (if empty, uses first track from the queue)
|
|
|
|
* @param {PlayOptions={}} options The options
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2021-06-13 18:09:25 +05:00
|
|
|
async play(src?: Track, options: PlayOptions = {}): Promise<void> {
|
2021-06-13 13:06:19 +05:00
|
|
|
if (!this.connection || !this.connection.voiceConnection) throw new Error("Voice connection is not available, use <Queue>.connect()!");
|
|
|
|
if (src && (this.playing || this.tracks.length) && !options.immediate) return this.addTrack(src);
|
2021-06-19 23:10:38 +05:00
|
|
|
const track = options.filtersUpdate && !options.immediate ? src || this.current : src ?? this.tracks.shift();
|
2021-06-11 23:19:52 +05:00
|
|
|
if (!track) return;
|
2021-06-13 21:47:04 +05:00
|
|
|
|
|
|
|
if (!options.filtersUpdate) {
|
|
|
|
this.previousTracks = this.previousTracks.filter((x) => x._trackID !== track._trackID);
|
|
|
|
this.previousTracks.push(track);
|
|
|
|
}
|
|
|
|
|
2021-06-12 18:22:45 +05:00
|
|
|
let stream;
|
2021-06-11 23:19:52 +05:00
|
|
|
if (["youtube", "spotify"].includes(track.raw.source)) {
|
2021-06-13 18:09:25 +05:00
|
|
|
if (track.raw.source === "spotify" && !track.raw.engine) {
|
|
|
|
track.raw.engine = await YouTube.search(`${track.author} ${track.title}`, { type: "video" })
|
|
|
|
.then((x) => x[0].url)
|
|
|
|
.catch(() => null);
|
|
|
|
}
|
|
|
|
const link = track.raw.source === "spotify" ? track.raw.engine : track.url;
|
|
|
|
if (!link) return void this.play(this.tracks.shift(), { immediate: true });
|
|
|
|
|
|
|
|
stream = ytdl(link, {
|
2021-06-14 19:44:15 +05:00
|
|
|
...this.options.ytdlOptions,
|
|
|
|
// discord-ytdl-core
|
2021-06-11 23:19:52 +05:00
|
|
|
opusEncoded: false,
|
2021-06-12 11:37:41 +05:00
|
|
|
fmt: "s16le",
|
2021-06-20 10:37:59 +05:00
|
|
|
encoderArgs: options.encoderArgs ?? this._activeFilters.length ? ["-af", AudioFilters.create(this._activeFilters)] : [],
|
|
|
|
seek: options.seek ? options.seek / 1000 : 0
|
2021-06-20 00:48:48 +05:00
|
|
|
}).on("error", (err) => (err.message.toLowerCase().includes("premature close") ? null : this.player.emit("error", this, err)));
|
2021-06-11 23:19:52 +05:00
|
|
|
} else {
|
2021-06-18 00:09:02 +05:00
|
|
|
stream = ytdl
|
|
|
|
.arbitraryStream(
|
|
|
|
track.raw.source === "soundcloud" ? await track.raw.engine.downloadProgressive() : typeof track.raw.engine === "function" ? await track.raw.engine() : track.raw.engine,
|
|
|
|
{
|
|
|
|
opusEncoded: false,
|
|
|
|
fmt: "s16le",
|
2021-06-20 10:37:59 +05:00
|
|
|
encoderArgs: options.encoderArgs ?? this._activeFilters.length ? ["-af", AudioFilters.create(this._activeFilters)] : [],
|
|
|
|
seek: options.seek ? options.seek / 1000 : 0
|
2021-06-18 00:09:02 +05:00
|
|
|
}
|
|
|
|
)
|
2021-06-20 00:48:48 +05:00
|
|
|
.on("error", (err) => (err.message.toLowerCase().includes("premature close") ? null : this.player.emit("error", this, err)));
|
2021-06-11 23:19:52 +05:00
|
|
|
}
|
|
|
|
|
2021-06-12 18:22:45 +05:00
|
|
|
const resource: AudioResource<Track> = this.connection.createStream(stream, {
|
|
|
|
type: StreamType.Raw,
|
|
|
|
data: track
|
|
|
|
});
|
|
|
|
|
2021-06-19 23:10:38 +05:00
|
|
|
if (options.seek) this._streamTime = options.seek;
|
|
|
|
|
2021-06-12 00:18:53 +05:00
|
|
|
const dispatcher = await this.connection.playStream(resource);
|
2021-06-20 10:37:59 +05:00
|
|
|
dispatcher.setVolume(this.options.initialVolume);
|
2021-06-11 23:19:52 +05:00
|
|
|
|
2021-06-14 09:46:29 +05:00
|
|
|
// need to use these events here
|
2021-06-13 13:06:19 +05:00
|
|
|
dispatcher.once("start", () => {
|
2021-06-11 23:19:52 +05:00
|
|
|
this.playing = true;
|
2021-06-12 11:37:41 +05:00
|
|
|
if (!options.filtersUpdate) this.player.emit("trackStart", this, this.current);
|
2021-06-11 23:19:52 +05:00
|
|
|
});
|
|
|
|
|
2021-06-20 12:13:27 +05:00
|
|
|
dispatcher.once("finish", async () => {
|
2021-06-11 23:19:52 +05:00
|
|
|
this.playing = false;
|
2021-06-12 11:37:41 +05:00
|
|
|
if (options.filtersUpdate) return;
|
|
|
|
|
2021-06-19 23:10:38 +05:00
|
|
|
this._streamTime = 0;
|
|
|
|
|
2021-06-13 17:43:36 +05:00
|
|
|
if (!this.tracks.length && this.repeatMode === QueueRepeatMode.OFF) {
|
2021-06-18 00:09:02 +05:00
|
|
|
if (this.options.leaveOnEnd) this.destroy();
|
2021-06-11 23:19:52 +05:00
|
|
|
this.player.emit("queueEnd", this);
|
|
|
|
} else {
|
2021-06-20 12:13:27 +05:00
|
|
|
if (this.repeatMode !== QueueRepeatMode.AUTOPLAY) {
|
|
|
|
if (this.repeatMode === QueueRepeatMode.TRACK) return void this.play(Util.last(this.previousTracks), { immediate: true });
|
|
|
|
if (this.repeatMode === QueueRepeatMode.QUEUE) this.tracks.push(Util.last(this.previousTracks));
|
|
|
|
const nextTrack = this.tracks.shift();
|
|
|
|
this.play(nextTrack, { immediate: true });
|
2021-06-20 12:36:05 +05:00
|
|
|
return;
|
2021-06-20 12:13:27 +05:00
|
|
|
} else {
|
2021-06-20 12:36:05 +05:00
|
|
|
if (![track.source, track.raw?.source].includes("youtube")) {
|
2021-06-20 12:13:27 +05:00
|
|
|
if (this.options.leaveOnEnd) this.destroy();
|
|
|
|
return void this.player.emit("queueEnd", this);
|
|
|
|
}
|
2021-06-20 12:36:16 +05:00
|
|
|
const info = await ytdl
|
|
|
|
.getInfo(track.url)
|
|
|
|
.then((x) => x.related_videos[0])
|
|
|
|
.catch(() => {});
|
2021-06-20 12:13:27 +05:00
|
|
|
if (!info) {
|
|
|
|
if (this.options.leaveOnEnd) this.destroy();
|
|
|
|
return void this.player.emit("queueEnd", this);
|
2021-06-20 12:36:16 +05:00
|
|
|
}
|
2021-06-20 12:13:27 +05:00
|
|
|
|
2021-06-20 12:36:05 +05:00
|
|
|
const nextTrack = new Track(this.player, {
|
|
|
|
title: info.title,
|
|
|
|
url: `https://www.youtube.com/watch?v=${info.id}`,
|
|
|
|
duration: info.length_seconds ? Util.buildTimeCode(Util.parseMS(info.length_seconds * 1000)) : "0:00",
|
|
|
|
description: "",
|
|
|
|
thumbnail: Util.last(info.thumbnails).url,
|
|
|
|
views: parseInt(info.view_count.replace(/[^0-9]/g, "")),
|
|
|
|
author: typeof info.author === "string" ? info.author : info.author.name,
|
|
|
|
requestedBy: track.requestedBy,
|
|
|
|
source: "youtube"
|
|
|
|
});
|
|
|
|
|
|
|
|
this.play(nextTrack, { immediate: true });
|
2021-06-20 12:13:27 +05:00
|
|
|
}
|
2021-06-11 23:19:52 +05:00
|
|
|
}
|
|
|
|
});
|
2021-06-11 16:50:43 +05:00
|
|
|
}
|
2021-06-11 19:57:49 +05:00
|
|
|
|
|
|
|
*[Symbol.iterator]() {
|
|
|
|
yield* this.tracks;
|
|
|
|
}
|
2021-06-11 23:19:52 +05:00
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* JSON representation of this queue
|
|
|
|
* @returns {object}
|
|
|
|
*/
|
2021-06-11 23:19:52 +05:00
|
|
|
toJSON() {
|
|
|
|
return {
|
|
|
|
guild: this.guild.id,
|
2021-06-20 12:36:05 +05:00
|
|
|
voiceChannel: this.connection?.channel?.id,
|
2021-06-11 23:19:52 +05:00
|
|
|
options: this.options,
|
|
|
|
tracks: this.tracks.map((m) => m.toJSON())
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2021-06-14 22:51:54 +05:00
|
|
|
/**
|
|
|
|
* String representation of this queue
|
|
|
|
* @returns {string}
|
|
|
|
*/
|
2021-06-11 23:19:52 +05:00
|
|
|
toString() {
|
|
|
|
if (!this.tracks.length) return "No songs available to display!";
|
|
|
|
return `**Upcoming Songs:**\n${this.tracks.map((m, i) => `${i + 1}. **${m.title}**`).join("\n")}`;
|
|
|
|
}
|
2021-06-11 15:32:22 +05:00
|
|
|
}
|
|
|
|
|
2021-06-13 23:28:37 +05:00
|
|
|
export { Queue };
|