UNPKG

riffy-extended

Version:

Riffy is a pro lavalink client. It is designed to be simple and easy to use, with a focus on stability and more features.

398 lines (323 loc) 12.9 kB
const { EventEmitter } = require("events"); const { Connection } = require("./Connection"); const { Filters } = require("./Filters"); const { Queue } = require("./Queue"); const { soundcloud, spotify } = require('../functions/autoPlay'); class Player extends EventEmitter { constructor(riffy, node, options) { super(); this.riffy = riffy; this.node = node; this.options = options; this.guildId = options.guildId; this.textChannel = options.textChannel; this.voiceChannel = options.voiceChannel; this.connection = new Connection(this); this.filters = new Filters(this); this.mute = options.mute ?? false; this.deaf = options.deaf ?? false; this.volume = options.defaultVolume ?? 100; this.loop = options.loop ?? "none"; this.data = {}; this.queue = new Queue(); this.position = 0; this.current = null; this.previous = null; this.playing = false; this.paused = false; this.connected = false; this.timestamp = 0; this.ping = 0; this.isAutoplay = false; this.on("playerUpdate", (packet) => { (this.connected = packet.state.connected), (this.position = packet.state.position), (this.ping = packet.state.ping); this.timestamp = packet.state.time; this.riffy.emit("playerUpdate", this, packet); }); this.on("event", (data) => { this.handleEvent(data) }); } async play() { if (!this.connected) throw new Error("Player connection is not initiated. Kindly use Riffy.createConnection() and establish a connection, TIP: Check if Guild Voice States intent is set/provided & 'updateVoiceState' is used in the raw(Gateway Raw) event"); if (!this.queue.length) return; this.current = this.queue.shift(); if (!this.current.track) { this.current = await this.current.resolve(this.riffy); } this.playing = true; this.position = 0; const { track } = this.current; this.node.rest.updatePlayer({ guildId: this.guildId, data: { track: { encoded: track, }, }, }); return this; } /** * * @param {this} player * @returns */ async autoplay(player) { if (!player) { if (player == null) { this.isAutoplay = false; return this; } else if (player == false) { this.isAutoplay = false; return this; } else throw new Error("Missing argument. Quick Fix: player.autoplay(player)"); } this.isAutoplay = true; // If ran on queueEnd event if (player.previous) { if (player.previous.info.sourceName === "youtube") { try { let data = `https://www.youtube.com/watch?v=${player.previous.info.identifier}&list=RD${player.previous.info.identifier}`; let response = await this.riffy.resolve({ query: data, source: "ytmsearch", requester: player.previous.info.requester }); if (this.node.rest.version === "v4") { if (!response || !response.tracks || ["error", "empty"].includes(response.loadType)) return this.stop(); } else { if (!response || !response.tracks || ["LOAD_FAILED", "NO_MATCHES"].includes(response.loadType)) return this.stop(); } let track = response.tracks[Math.floor(Math.random() * Math.floor(response.tracks.length))]; this.queue.push(track); this.play(); return this } catch (e) { return this.stop(); } } else if (player.previous.info.sourceName === "soundcloud") { try { soundcloud(player.previous.info.uri).then(async (data) => { let response = await this.riffy.resolve({ query: data, source: "scsearch", requester: player.previous.info.requester }); if (this.node.rest.version === "v4") { if (!response || !response.tracks || ["error", "empty"].includes(response.loadType)) return this.stop(); } else { if (!response || !response.tracks || ["LOAD_FAILED", "NO_MATCHES"].includes(response.loadType)) return this.stop(); } let track = response.tracks[Math.floor(Math.random() * Math.floor(response.tracks.length))]; this.queue.push(track); this.play(); return this; }) } catch (e) { console.log(e); return this.stop(); } } else if (player.previous.info.sourceName === "spotify") { try { spotify(player.previous.info.identifier).then(async (data) => { const response = await this.riffy.resolve({ query: `https://open.spotify.com/track/${data}`, requester: player.previous.info.requester }); if (this.node.rest.version === "v4") { if (!response || !response.tracks || ["error", "empty"].includes(response.loadType)) return this.stop(); } else { if (!response || !response.tracks || ["LOAD_FAILED", "NO_MATCHES"].includes(response.loadType)) return this.stop(); } let track = response.tracks[Math.floor(Math.random() * Math.floor(response.tracks.length))]; this.queue.push(track); this.play(); return this; }) } catch (e) { console.log(e); return this.stop(); } } } else return this; } connect(options = this) { const { guildId, voiceChannel, deaf = true, mute = false } = options; this.send({ guild_id: guildId, channel_id: voiceChannel, self_deaf: deaf, self_mute: mute, }); this.connected = true this.riffy.emit("debug", this.guildId, `Player has informed the Discord Gateway to Establish Voice Connectivity in ${voiceChannel} Voice Channel, Awaiting Confirmation(Via Voice State Update & Voice Server Update events)`); } stop() { this.position = 0; this.playing = false; this.node.rest.updatePlayer({ guildId: this.guildId, data: { track: { encoded: null } }, }); return this; } pause(toggle = true) { this.node.rest.updatePlayer({ guildId: this.guildId, data: { paused: toggle }, }); this.playing = !toggle; this.paused = toggle; return this; } seek(position) { const trackLength = this.current.info.length; this.position = Math.max(0, Math.min(trackLength, position)); this.node.rest.updatePlayer({ guildId: this.guildId, data: { position } }); } setVolume(volume) { if (volume < 0 || volume > 1000) { throw new Error("[Volume] Volume must be between 0 to 1000"); } this.node.rest.updatePlayer({ guildId: this.guildId, data: { volume } }); this.volume = volume; return this; } setLoop(mode) { if (!mode) { throw new Error("You must provide the loop mode as an argument for setLoop"); } if (!["none", "track", "queue"].includes(mode)) { throw new Error("setLoop arguments must be 'none', 'track', or 'queue'"); } this.loop = mode; return this; } setTextChannel(channel) { if (typeof channel !== "string") throw new TypeError("Channel must be a non-empty string."); this.textChannel = channel; return this; } setVoiceChannel(channel, options) { if (typeof channel !== "string") throw new TypeError("Channel must be a non-empty string."); if (this.connected && channel === this.voiceChannel) { throw new ReferenceError(`Player is already connected to ${channel}`); } this.voiceChannel = channel; if (options) { this.mute = options.mute ?? this.mute; this.deaf = options.deaf ?? this.deaf; } this.connect({ deaf: this.deaf, guildId: this.guildId, voiceChannel: this.voiceChannel, textChannel: this.textChannel, mute: this.mute, }); return this; } disconnect() { if (!this.voiceChannel) { return; } this.connected = false; this.send({ guild_id: this.guildId, channel_id: null, self_mute: false, self_deaf: false, }); this.voiceChannel = null; return this; } destroy() { this.disconnect(); this.node.rest.destroyPlayer(this.guildId); this.riffy.emit("playerDisconnect", this); this.riffy.emit("debug", this.guildId, "Destroyed the player"); this.riffy.players.delete(this.guildId); } async handleEvent(payload) { const player = this.riffy.players.get(payload.guildId); if (!player) return; const track = this.current; // if (this.node.rest.version === "v4") { // track.info.thumbnail = await track.info.thumbnail; // } else { // track.info.thumbnail = await track.info.thumbnail; // } switch (payload.type) { case "TrackStartEvent": this.trackStart(player, track, payload); break; case "TrackEndEvent": this.trackEnd(player, track, payload); break; case "TrackExceptionEvent": this.trackError(player, track, payload); break; case "TrackStuckEvent": this.trackStuck(player, track, payload); break; case "WebSocketClosedEvent": this.socketClosed(player, payload); break; default: const error = new Error(`Node encountered an unknown event: '${payload.type}'`); this.riffy.emit("nodeError", this, error); break; } } trackStart(player, track, payload) { this.playing = true; this.paused = false; this.riffy.emit("trackStart", player, track, payload); } trackEnd(player, track, payload) { this.previous = track; if (this.loop === "track") { player.queue.unshift(this.previous); this.riffy.emit("trackEnd", player, track, payload); return player.play(); } else if (track && this.loop === "queue") { player.queue.push(this.previous); this.riffy.emit("trackEnd", player, track, payload); return player.play(); } if (player.queue.length === 0) { this.playing = false; return this.riffy.emit("queueEnd", player); } else if (player.queue.length > 0) { this.riffy.emit("trackEnd", player, track, payload); return player.play(); } this.playing = false; this.riffy.emit("queueEnd", player); } trackError(player, track, payload) { this.riffy.emit("trackError", player, track, payload); this.stop(); } trackStuck(player, track, payload) { this.riffy.emit("trackStuck", player, track, payload); this.stop(); } socketClosed(player, payload) { if ([4015, 4009].includes(payload.code)) { this.send({ guild_id: payload.guildId, channel_id: this.voiceChannel, self_mute: this.mute, self_deaf: this.deaf, }); } this.riffy.emit("socketClosed", player, payload); this.pause(true); this.riffy.emit("debug", this.guildId, "Player paused, channel deleted, Or Client was kicked"); } set(key, value) { return this.data[key] = value; } get(key) { return this.data[key]; } send(data) { this.riffy.send({ op: 4, d: data }); } } module.exports = { Player };