erela.js
Version:
An easy-to-use Lavalink client for NodeJS.
355 lines (354 loc) • 11.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Player = void 0;
const Utils_1 = require("./Utils");
function check(options) {
if (!options)
throw new TypeError("PlayerOptions must not be empty.");
if (!/^\d+$/.test(options.guild))
throw new TypeError('Player option "guild" must be present and be a non-empty string.');
if (options.textChannel && !/^\d+$/.test(options.textChannel))
throw new TypeError('Player option "textChannel" must be a non-empty string.');
if (options.voiceChannel && !/^\d+$/.test(options.voiceChannel))
throw new TypeError('Player option "voiceChannel" must be a non-empty string.');
if (options.node && typeof options.node !== "string")
throw new TypeError('Player option "node" must be a non-empty string.');
if (typeof options.volume !== "undefined" &&
typeof options.volume !== "number")
throw new TypeError('Player option "volume" must be a number.');
if (typeof options.selfMute !== "undefined" &&
typeof options.selfMute !== "boolean")
throw new TypeError('Player option "selfMute" must be a boolean.');
if (typeof options.selfDeafen !== "undefined" &&
typeof options.selfDeafen !== "boolean")
throw new TypeError('Player option "selfDeafen" must be a boolean.');
}
class Player {
options;
/** The Queue for the Player. */
queue = new (Utils_1.Structure.get("Queue"))();
/** Whether the queue repeats the track. */
trackRepeat = false;
/** Whether the queue repeats the queue. */
queueRepeat = false;
/** The time the player is in the track. */
position = 0;
/** Whether the player is playing. */
playing = false;
/** Whether the player is paused. */
paused = false;
/** The volume for the player */
volume;
/** The Node for the Player. */
node;
/** The guild for the player. */
guild;
/** The voice channel for the player. */
voiceChannel = null;
/** The text channel for the player. */
textChannel = null;
/** The current state of the player. */
state = "DISCONNECTED";
/** The equalizer bands array. */
bands = new Array(15).fill(0.0);
/** The voice state object from Discord. */
voiceState;
/** The Manager. */
manager;
static _manager;
data = {};
/**
* Set custom data.
* @param key
* @param value
*/
set(key, value) {
this.data[key] = value;
}
/**
* Get custom data.
* @param key
*/
get(key) {
return this.data[key];
}
/** @hidden */
static init(manager) {
this._manager = manager;
}
/**
* Creates a new player, returns one if it already exists.
* @param options
*/
constructor(options) {
this.options = options;
if (!this.manager)
this.manager = Utils_1.Structure.get("Player")._manager;
if (!this.manager)
throw new RangeError("Manager has not been initiated.");
if (this.manager.players.has(options.guild)) {
return this.manager.players.get(options.guild);
}
check(options);
this.guild = options.guild;
this.voiceState = Object.assign({ op: "voiceUpdate", guildId: options.guild });
if (options.voiceChannel)
this.voiceChannel = options.voiceChannel;
if (options.textChannel)
this.textChannel = options.textChannel;
const node = this.manager.nodes.get(options.node);
this.node = node || this.manager.leastLoadNodes.first();
if (!this.node)
throw new RangeError("No available nodes.");
this.manager.players.set(options.guild, this);
this.manager.emit("playerCreate", this);
this.setVolume(options.volume ?? 100);
}
/**
* Same as Manager#search() but a shortcut on the player itself.
* @param query
* @param requester
*/
search(query, requester) {
return this.manager.search(query, requester);
}
/**
* Sets the players equalizer band on-top of the existing ones.
* @param bands
*/
setEQ(...bands) {
// Hacky support for providing an array
if (Array.isArray(bands[0]))
bands = bands[0];
if (!bands.length || !bands.every((band) => JSON.stringify(Object.keys(band).sort()) === '["band","gain"]'))
throw new TypeError("Bands must be a non-empty object array containing 'band' and 'gain' properties.");
for (const { band, gain } of bands)
this.bands[band] = gain;
this.node.send({
op: "equalizer",
guildId: this.guild,
bands: this.bands.map((gain, band) => ({ band, gain })),
});
return this;
}
/** Clears the equalizer bands. */
clearEQ() {
this.bands = new Array(15).fill(0.0);
this.node.send({
op: "equalizer",
guildId: this.guild,
bands: this.bands.map((gain, band) => ({ band, gain })),
});
return this;
}
/** Connect to the voice channel. */
connect() {
if (!this.voiceChannel)
throw new RangeError("No voice channel has been set.");
this.state = "CONNECTING";
this.manager.options.send(this.guild, {
op: 4,
d: {
guild_id: this.guild,
channel_id: this.voiceChannel,
self_mute: this.options.selfMute || false,
self_deaf: this.options.selfDeafen || false,
},
});
this.state = "CONNECTED";
return this;
}
/** Disconnect from the voice channel. */
disconnect() {
if (this.voiceChannel === null)
return this;
this.state = "DISCONNECTING";
this.pause(true);
this.manager.options.send(this.guild, {
op: 4,
d: {
guild_id: this.guild,
channel_id: null,
self_mute: false,
self_deaf: false,
},
});
this.voiceChannel = null;
this.state = "DISCONNECTED";
return this;
}
/** Destroys the player. */
destroy(disconnect = true) {
this.state = "DESTROYING";
if (disconnect) {
this.disconnect();
}
this.node.send({
op: "destroy",
guildId: this.guild,
});
this.manager.emit("playerDestroy", this);
this.manager.players.delete(this.guild);
}
/**
* Sets the player voice channel.
* @param channel
*/
setVoiceChannel(channel) {
if (typeof channel !== "string")
throw new TypeError("Channel must be a non-empty string.");
this.voiceChannel = channel;
this.connect();
return this;
}
/**
* Sets the player text channel.
* @param channel
*/
setTextChannel(channel) {
if (typeof channel !== "string")
throw new TypeError("Channel must be a non-empty string.");
this.textChannel = channel;
return this;
}
async play(optionsOrTrack, playOptions) {
if (typeof optionsOrTrack !== "undefined" &&
Utils_1.TrackUtils.validate(optionsOrTrack)) {
if (this.queue.current)
this.queue.previous = this.queue.current;
this.queue.current = optionsOrTrack;
}
if (!this.queue.current)
throw new RangeError("No current track.");
const finalOptions = playOptions
? playOptions
: ["startTime", "endTime", "noReplace"].every((v) => Object.keys(optionsOrTrack || {}).includes(v))
? optionsOrTrack
: {};
if (Utils_1.TrackUtils.isUnresolvedTrack(this.queue.current)) {
try {
this.queue.current = await Utils_1.TrackUtils.getClosestTrack(this.queue.current);
}
catch (error) {
this.manager.emit("trackError", this, this.queue.current, error);
if (this.queue[0])
return this.play(this.queue[0]);
return;
}
}
const options = {
op: "play",
guildId: this.guild,
track: this.queue.current.track,
...finalOptions,
};
if (typeof options.track !== "string") {
options.track = options.track.track;
}
await this.node.send(options);
}
/**
* Sets the player volume.
* @param volume
*/
setVolume(volume) {
volume = Number(volume);
if (isNaN(volume))
throw new TypeError("Volume must be a number.");
this.volume = Math.max(Math.min(volume, 1000), 0);
this.node.send({
op: "volume",
guildId: this.guild,
volume: this.volume,
});
return this;
}
/**
* Sets the track repeat.
* @param repeat
*/
setTrackRepeat(repeat) {
if (typeof repeat !== "boolean")
throw new TypeError('Repeat can only be "true" or "false".');
if (repeat) {
this.trackRepeat = true;
this.queueRepeat = false;
}
else {
this.trackRepeat = false;
this.queueRepeat = false;
}
return this;
}
/**
* Sets the queue repeat.
* @param repeat
*/
setQueueRepeat(repeat) {
if (typeof repeat !== "boolean")
throw new TypeError('Repeat can only be "true" or "false".');
if (repeat) {
this.trackRepeat = false;
this.queueRepeat = true;
}
else {
this.trackRepeat = false;
this.queueRepeat = false;
}
return this;
}
/** Stops the current track, optionally give an amount to skip to, e.g 5 would play the 5th song. */
stop(amount) {
if (typeof amount === "number" && amount > 1) {
if (amount > this.queue.length)
throw new RangeError("Cannot skip more than the queue length.");
this.queue.splice(0, amount - 1);
}
this.node.send({
op: "stop",
guildId: this.guild,
});
return this;
}
/**
* Pauses the current track.
* @param pause
*/
pause(pause) {
if (typeof pause !== "boolean")
throw new RangeError('Pause can only be "true" or "false".');
// If already paused or the queue is empty do nothing https://github.com/MenuDocs/erela.js/issues/58
if (this.paused === pause || !this.queue.totalSize)
return this;
this.playing = !pause;
this.paused = pause;
this.node.send({
op: "pause",
guildId: this.guild,
pause,
});
return this;
}
/**
* Seeks to the position in the current track.
* @param position
*/
seek(position) {
if (!this.queue.current)
return undefined;
position = Number(position);
if (isNaN(position)) {
throw new RangeError("Position must be a number.");
}
if (position < 0 || position > this.queue.current.duration)
position = Math.max(Math.min(position, this.queue.current.duration), 0);
this.position = position;
this.node.send({
op: "seek",
guildId: this.guild,
position,
});
return this;
}
}
exports.Player = Player;