UNPKG

larakazagumo

Version:

A shoukaku wrapper with built-in queue support.

388 lines (387 loc) 16.7 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.KazagumoPlayer = void 0; const KazagumoQueue_1 = require("./Supports/KazagumoQueue"); const Interfaces_1 = require("../Modules/Interfaces"); const KazagumoTrack_1 = require("./Supports/KazagumoTrack"); class KazagumoPlayer { /** * Initialize the player * @param kazagumo Kazagumo instance * @param player Shoukaku's Player instance * @param options Kazagumo options * @param customData private readonly customData */ constructor(kazagumo, player, options, customData) { var _a, _b; this.customData = customData; /** * Get the current state of the player */ this.state = Interfaces_1.PlayerState.CONNECTING; /** * Paused state of the player */ this.paused = false; /** * Whether the player is playing or not */ this.playing = false; /** * Loop status */ this.loop = 'none'; /** * Player's volume in percentage (default 100%) */ this.volume = 100; /** * Player's custom data */ this.data = new Map(); this.options = options; this.kazagumo = kazagumo; this.shoukaku = player; this.guildId = options.guildId; this.voiceId = options.voiceId; this.textId = options.textId; this.queue = new ((_b = (_a = this.options.extends) === null || _a === void 0 ? void 0 : _a.queue) !== null && _b !== void 0 ? _b : KazagumoQueue_1.KazagumoQueue)(this); if (options.volume !== 100) this.setVolume(options.volume); this.search = (typeof this.options.searchWithSameNode === 'boolean' ? this.options.searchWithSameNode : true) ? (query, opt) => kazagumo.search.bind(kazagumo)(query, opt ? Object.assign(Object.assign({}, opt), { nodeName: this.shoukaku.node.name }) : undefined) : kazagumo.search.bind(kazagumo); this.shoukaku.on('start', () => { this.playing = true; this.emit(Interfaces_1.Events.PlayerStart, this, this.queue.current); }); this.shoukaku.on('end', (data) => { var _a, _b, _c; // This event emits STOPPED reason when destroying, so return to prevent double emit if (this.state === Interfaces_1.PlayerState.DESTROYING || this.state === Interfaces_1.PlayerState.DESTROYED) return this.emit(Interfaces_1.Events.Debug, `Player ${this.guildId} destroyed from end event`); if (data.reason === 'replaced') return this.emit(Interfaces_1.Events.PlayerEnd, this); if (['loadFailed', 'cleanup'].includes(data.reason)) { if (this.queue.current && !((_a = this.queue.previous) !== null && _a !== void 0 ? _a : []).find((x) => { var _a, _b; return x.identifier === ((_a = this.queue.current) === null || _a === void 0 ? void 0 : _a.identifier) && x.title === ((_b = this.queue.current) === null || _b === void 0 ? void 0 : _b.title); })) this.queue.previous = (_b = [this.queue.current].concat(this.queue.previous)) !== null && _b !== void 0 ? _b : []; this.emit(Interfaces_1.Events.PlayerEnd, this, this.queue.current); this.queue.current = null; this.playing = false; if (!this.queue.length) return this.emit(Interfaces_1.Events.PlayerEmpty, this); return this.play(); } if (this.loop === 'track' && this.queue.current) this.queue.unshift(this.queue.current); if (this.loop === 'queue' && this.queue.current) this.queue.push(this.queue.current); if (this.queue.current && !this.queue.previous.find((x) => { var _a, _b; return x.identifier === ((_a = this.queue.current) === null || _a === void 0 ? void 0 : _a.identifier) && x.title === ((_b = this.queue.current) === null || _b === void 0 ? void 0 : _b.title); })) this.queue.previous = (_c = [this.queue.current].concat(this.queue.previous)) !== null && _c !== void 0 ? _c : []; const currentSong = this.queue.current; this.emit(Interfaces_1.Events.PlayerEnd, this, currentSong); this.queue.current = null; if (!this.queue.length) { this.playing = false; return this.emit(Interfaces_1.Events.PlayerEmpty, this); } return this.play(); }); this.shoukaku.on('closed', (data) => { this.playing = false; this.emit(Interfaces_1.Events.PlayerClosed, this, data); }); this.shoukaku.on('exception', (data) => { this.playing = false; this.emit(Interfaces_1.Events.PlayerException, this, data); }); this.shoukaku.on('update', (data) => this.emit(Interfaces_1.Events.PlayerUpdate, this, data)); this.shoukaku.on('stuck', (data) => this.emit(Interfaces_1.Events.PlayerStuck, this, data)); this.shoukaku.on('resumed', () => this.emit(Interfaces_1.Events.PlayerResumed, this)); // @ts-ignore this.shoukaku.on(Interfaces_1.Events.QueueUpdate, (referencePlayer, queue) => this.kazagumo.emit(Interfaces_1.Events.QueueUpdate, referencePlayer, queue)); } // /** // * Get volume // */ // public get volume(): number { // return this.shoukaku.filters.volume || 1; // } /** * Get player position */ get position() { return this.shoukaku.position; } /** * Get filters */ get filters() { return this.shoukaku.filters; } get node() { return this.shoukaku.node; } /** * Pause the player * @param pause Whether to pause or not * @returns KazagumoPlayer */ pause(pause) { if (typeof pause !== 'boolean') throw new Interfaces_1.KazagumoError(1, 'pause must be a boolean'); if (this.paused === pause || !this.queue.totalSize) return this; this.paused = pause; this.playing = !pause; this.shoukaku.setPaused(pause); return this; } /** * Set text channel * @param textId Text channel ID * @returns KazagumoPlayer */ setTextChannel(textId) { if (this.state === Interfaces_1.PlayerState.DESTROYED) throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed'); this.textId = textId; return this; } /** * Set voice channel and move the player to the voice channel * @param voiceId Voice channel ID * @returns KazagumoPlayer */ setVoiceChannel(voiceId) { if (this.state === Interfaces_1.PlayerState.DESTROYED) throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed'); this.state = Interfaces_1.PlayerState.CONNECTING; this.voiceId = voiceId; this.kazagumo.KazagumoOptions.send(this.guildId, { op: 4, d: { guild_id: this.guildId, channel_id: this.voiceId, self_mute: false, self_deaf: this.options.deaf, }, }); this.emit(Interfaces_1.Events.Debug, `Player ${this.guildId} moved to voice channel ${voiceId}`); return this; } /** * Get the previous track from the queue * @param remove Whether to remove the track from the previous list or not. Best to set to true if you want to play it */ getPrevious(remove = false) { if (remove) return this.queue.previous.shift(); return this.queue.previous[0]; } /** * Set loop mode * @param [loop] Loop mode * @returns KazagumoPlayer */ setLoop(loop) { if (loop === undefined) { if (this.loop === 'none') this.loop = 'queue'; else if (this.loop === 'queue') this.loop = 'track'; else if (this.loop === 'track') this.loop = 'none'; return this; } if (loop === 'none' || loop === 'queue' || loop === 'track') { this.loop = loop; return this; } throw new Interfaces_1.KazagumoError(1, "loop must be one of 'none', 'queue', 'track'"); } /** * Play a track * @param track Track to play * @param options Play options * @returns KazagumoPlayer */ play(track, options) { return __awaiter(this, void 0, void 0, function* () { var _a; if (this.state === Interfaces_1.PlayerState.DESTROYED) throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed'); if (track && !(track instanceof KazagumoTrack_1.KazagumoTrack)) throw new Interfaces_1.KazagumoError(1, 'track must be a KazagumoTrack'); if (!track && !this.queue.totalSize) throw new Interfaces_1.KazagumoError(1, 'No track is available to play'); if (!options || typeof options.replaceCurrent !== 'boolean') options = Object.assign(Object.assign({}, options), { replaceCurrent: false }); if (track) { if (!options.replaceCurrent && this.queue.current) this.queue.unshift(this.queue.current); this.queue.current = track; } else if (!this.queue.current) this.queue.current = this.queue.shift(); if (!this.queue.current) throw new Interfaces_1.KazagumoError(1, 'No track is available to play'); const current = this.queue.current; current.setKazagumo(this.kazagumo); let errorMessage; const resolveResult = yield current.resolve({ player: this }).catch((e) => { errorMessage = e.message; return null; }); if (!resolveResult) { this.emit(Interfaces_1.Events.PlayerResolveError, this, current, errorMessage); this.emit(Interfaces_1.Events.Debug, `Player ${this.guildId} resolve error: ${errorMessage}`); this.queue.current = null; this.queue.size ? yield this.play() : this.emit(Interfaces_1.Events.PlayerEmpty, this); return this; } let playOptions = { track: { encoded: current.track, userData: (_a = current.requester) !== null && _a !== void 0 ? _a : {} } }; if (options) playOptions = Object.assign(Object.assign({}, playOptions), options); yield this.shoukaku.playTrack(playOptions); return this; }); } /** * Skip the current track * @returns KazagumoPlayer */ skip() { if (this.state === Interfaces_1.PlayerState.DESTROYED) throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed'); this.shoukaku.stopTrack(); return this; } /** * Seek to a position * @param position Position in seconds * @returns KazagumoPlayer */ seek(position) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; if (this.state === Interfaces_1.PlayerState.DESTROYED) throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed'); if (!this.queue.current) throw new Interfaces_1.KazagumoError(1, "Player has no current track in it's queue"); if (!this.queue.current.isSeekable) throw new Interfaces_1.KazagumoError(1, "The current track isn't seekable"); position = Number(position); if (isNaN(position)) throw new Interfaces_1.KazagumoError(1, 'position must be a number'); if (position < 0 || position > ((_a = this.queue.current.length) !== null && _a !== void 0 ? _a : 0)) position = Math.max(Math.min(position, (_b = this.queue.current.length) !== null && _b !== void 0 ? _b : 0), 0); this.queue.current.position = position; yield this.shoukaku.seekTo(position); return this; }); } /** * Set the volume in percentage (default 100%) * @param volume Volume * @returns KazagumoPlayer */ setVolume(volume) { return __awaiter(this, void 0, void 0, function* () { if (this.state === Interfaces_1.PlayerState.DESTROYED) throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed'); if (isNaN(volume)) throw new Interfaces_1.KazagumoError(1, 'volume must be a number'); yield this.node.rest.updatePlayer({ guildId: this.guildId, playerOptions: { volume, }, }); this.volume = volume; return this; }); } /** * Connect to the voice channel * @returns KazagumoPlayer */ connect() { if (this.state === Interfaces_1.PlayerState.DESTROYED) throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed'); if (this.state === Interfaces_1.PlayerState.CONNECTED || !!this.voiceId) throw new Interfaces_1.KazagumoError(1, 'Player is already connected'); this.state = Interfaces_1.PlayerState.CONNECTING; this.kazagumo.KazagumoOptions.send(this.guildId, { op: 4, d: { guild_id: this.guildId, channel_id: this.voiceId, self_mute: false, self_deaf: this.options.deaf, }, }); this.state = Interfaces_1.PlayerState.CONNECTED; this.emit(Interfaces_1.Events.Debug, `Player ${this.guildId} connected`); return this; } /** * Disconnect from the voice channel * @returns KazagumoPlayer */ disconnect() { if (this.state === Interfaces_1.PlayerState.DISCONNECTED || !this.voiceId) throw new Interfaces_1.KazagumoError(1, 'Player is already disconnected'); this.state = Interfaces_1.PlayerState.DISCONNECTING; this.pause(true); this.kazagumo.KazagumoOptions.send(this.guildId, { op: 4, d: { guild_id: this.guildId, channel_id: null, self_mute: false, self_deaf: false, }, }); this.voiceId = null; this.state = Interfaces_1.PlayerState.DISCONNECTED; this.emit(Interfaces_1.Events.Debug, `Player disconnected; Guild id: ${this.guildId}`); return this; } /** * Destroy the player * @returns KazagumoPlayer */ destroy() { return __awaiter(this, void 0, void 0, function* () { if (this.state === Interfaces_1.PlayerState.DESTROYING || this.state === Interfaces_1.PlayerState.DESTROYED) throw new Interfaces_1.KazagumoError(1, 'Player is already destroyed'); this.disconnect(); this.state = Interfaces_1.PlayerState.DESTROYING; this.shoukaku.clean(); yield this.kazagumo.shoukaku.leaveVoiceChannel(this.guildId); yield this.shoukaku.destroy(); this.shoukaku.removeAllListeners(); this.kazagumo.players.delete(this.guildId); this.state = Interfaces_1.PlayerState.DESTROYED; this.emit(Interfaces_1.Events.PlayerDestroy, this); this.emit(Interfaces_1.Events.Debug, `Player destroyed; Guild id: ${this.guildId}`); return this; }); } emit(event, ...args) { this.kazagumo.emit(event, ...args); } } exports.KazagumoPlayer = KazagumoPlayer;