diff --git a/.gitignore b/.gitignore index d8e9629..1cfa9a7 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,4 @@ package-lock.json # custom -test/config.json +test/config.json \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..9cf9495 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false \ No newline at end of file diff --git a/package.json b/package.json index c385e6e..0723dbf 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lavalink.js", "version": "0.1.1", "license": "MIT", - "description": "A Lavalink client for eris", + "description": "A Lavalink client for Discord.js", "homepage": "https://github.com/briantanner/lavalink.js", "author": { "name": "Brian Tanner", @@ -20,6 +20,9 @@ "dependencies": { "ws": "^3.1.0" }, + "peerDependencies": { + "eventemitter3": "^3.0.0" + }, "devDependencies": { "discord.js": "^11.2.1", "docdash": "^0.4.0", diff --git a/src/Lavalink.js b/src/Lavalink.js index cc37304..ce6f4b6 100644 --- a/src/Lavalink.js +++ b/src/Lavalink.js @@ -14,15 +14,15 @@ try { * Represents a Lavalink node * @extends EventEmitter * @prop {string} host The hostname for the node - * @prop {number} port The port number for the node + * @prop {Number} port The port number for the node * @prop {string} address The full ws address for the node * @prop {string} region The region for this node * @prop {string} userId The client user id - * @prop {number} numShards The total number of shards the bot is running + * @prop {Number} numShards The total number of shards the bot is running * @prop {string} password The password used to connect * @prop {boolean} connected If it's connected to the node * @prop {boolean} draining True if this node will no longer take new connections - * @prop {object} stats The Lavalink node stats + * @prop {Object} stats The Lavalink node stats */ class Lavalink extends EventEmitter { @@ -37,6 +37,7 @@ class Lavalink extends EventEmitter { * @param {string} options.password The password for the Lavalink node * @param {Number} [options.timeout=5000] Optional timeout in ms used for the reconnect backoff */ + constructor(options) { super(); @@ -77,9 +78,7 @@ class Lavalink extends EventEmitter { this.ws.on('open', this.ready.bind(this)); this.ws.on('message', this.onMessage.bind(this)); this.ws.on('close', this.disconnectHandler); - this.ws.on('error', (err) => { - this.emit('error', err); - }); + this.ws.on('error', err => this.emit('error', err)); } /** diff --git a/src/Player.js b/src/Player.js index 6e8d7c6..a3413c2 100644 --- a/src/Player.js +++ b/src/Player.js @@ -1,3 +1,6 @@ +/** + * Created by NoobLance & Jacz on 01.01.2018. + */ let EventEmitter; try { @@ -12,13 +15,13 @@ try { * @prop {string} id Guild id for the player * @prop {PlayerManager} manager Reference to the player manager * @prop {Lavalink} node Lavalink node the player is connected to - * @prop {object} client The discord.js client + * @prop {Object} client The discord.js client * @prop {string} hostname Hostname of the lavalink node * @prop {string} guildId Guild ID * @prop {string} channelId Channel ID * @prop {boolean} ready If the connection is ready * @prop {boolean} playing If the player is playing - * @prop {object} state The lavalink player state + * @prop {Object} state The lavalink player state * @prop {string} track The lavalink track to play */ class Player extends EventEmitter { @@ -84,7 +87,7 @@ class Player extends EventEmitter { * @param {*} data The payload to send * @private */ - async sendEvent(data) { + sendEvent(data) { this.receivedEvents.push(data); this.node.send(data); process.nextTick(() => this.checkEventQueue()); @@ -183,7 +186,7 @@ class Player extends EventEmitter { this.node.send({ op: 'pause', guildId: this.guildId, - pause: pause + pause: Boolean(pause) }); } @@ -193,6 +196,8 @@ class Player extends EventEmitter { * @returns {void} */ seek(position) { + position = parseInt(position); + if (!Number.isInteger(position)) throw 'Position must be a integer'; this.node.send({ op: 'seek', guildId: this.guildId, @@ -206,6 +211,8 @@ class Player extends EventEmitter { * @returns {void} */ setVolume(volume) { + volume = parseInt(volume); + if (!Number.isInteger(volume)) throw 'Volume must be a integer'; this.node.send({ op: 'volume', guildId: this.guildId, diff --git a/src/PlayerManager.js b/src/PlayerManager.js index 551edb9..ad32ea4 100644 --- a/src/PlayerManager.js +++ b/src/PlayerManager.js @@ -1,3 +1,7 @@ +/** + * Created by NoobLance & Jacz on 01.01.2018. + * DISCLAIMER: Direct port from eris-lavalink + */ const Lavalink = require('./Lavalink'); const Player = require('./Player'); @@ -5,16 +9,16 @@ const Player = require('./Player'); * Player Manager * @extends Map * @prop {Player} baseObject The player class used to create new players - * @prop {object} client The discord.js client - * @prop {object} defaultRegions The default region config - * @prop {object} regions The region config being used + * @prop {Object} client The discord.js client + * @prop {Object} defaultRegions The default region config + * @prop {Object} regions The region config being used */ class PlayerManager extends Map { /** * PlayerManager constructor * @param {Client} client Eris client - * @param {Object[]} nodes The Lavalink nodes to connect to + * @param {Map} nodes The Lavalink nodes to connect to * @param {Object} [options] Setup options * @param {string} [options.defaultRegion] The default region * @param {Number} [options.failoverRate=250] Failover rate in ms @@ -84,8 +88,9 @@ class PlayerManager extends Map { * @param {string} host The hostname of the node */ removeNode(host) { - const node = this.nodes.get(host); if (!host) return; + const node = this.nodes.get(host); + if (!node) return; node.destroy(); this.nodes.delete(host); this.onDisconnect(node); @@ -96,7 +101,7 @@ class PlayerManager extends Map { * @private */ checkFailoverQueue() { - if (this.failoverQueue.length > 0) { + if (this.failoverQueue.length) { const fns = this.failoverQueue.splice(0, this.failoverLimit); for (const fn of fns) { this.processQueue(fn); @@ -111,7 +116,7 @@ class PlayerManager extends Map { * @private */ queueFailover(fn) { - if (this.failoverQueue.length > 0) { + if (this.failoverQueue.length) { this.failoverQueue.push(fn); } else { return this.processQueue(fn); @@ -135,7 +140,7 @@ class PlayerManager extends Map { * @private */ onError(node, err) { - this.client.emit(err); + this.client.emit('error', err); } /** @@ -156,16 +161,11 @@ class PlayerManager extends Map { * @private */ onReady() { - for (const player of [...this.values()]) { + for (const player of this.values()) { this.queueFailover(this.switchNode.bind(this, player)); } } - /** - * Client raw event listener - * @param {object} packet Packet received from the gateway - * @private - **/ onRaw(packet) { switch (packet.t) { case 'VOICE_SERVER_UPDATE': @@ -188,11 +188,11 @@ class PlayerManager extends Map { * @param {boolean} leave Whether to leave the channel or not on our side */ switchNode(player, leave) { - const { guildId, channelId, track } = player, - position = (player.state.position || 0) + (this.options.reconnectThreshold || 2000); + const { guildId, channelId, track } = player; + const position = (player.state.position || 0) + (this.options.reconnectThreshold || 2000); - const listeners = player.listeners('end'), - endListeners = []; + const listeners = player.listeners('end'); + const endListeners = []; if (listeners && listeners.length) { for (const listener of listeners) { @@ -224,8 +224,7 @@ class PlayerManager extends Map { this.set(guildId, player); }) .catch(err => { - player.emit('disconnect', err); - player.disconnect(); + player.disconnect(err); }); }); } @@ -233,7 +232,7 @@ class PlayerManager extends Map { /** * Called when a message is received from the voice node * @param {Lavalink} node The Lavalink node - * @param {*} message The message received + * @param {Object} message The message received * @returns {*} * @private */ @@ -288,7 +287,7 @@ class PlayerManager extends Map { this.client.ws.send(payload); - if (payload.op === 4 && payload.d.channel_id === null) { + if (payload.op === 4 && !payload.d.channel_id) { this.delete(payload.d.guild_id); } } @@ -303,14 +302,10 @@ class PlayerManager extends Map { if (!player) return; switch (message.type) { - case 'TrackEndEvent': - return player.onTrackEnd(message); - case 'TrackExceptionEvent': - return player.onTrackException(message); - case 'TrackStuckEvent': - return player.onTrackStuck(message); - default: - return player.emit('warn', `Unexpected event type: ${message.type}`); + case 'TrackEndEvent': return player.onTrackEnd(message); + case 'TrackExceptionEvent': return player.onTrackException(message); + case 'TrackStuckEvent': return player.onTrackStuck(message); + default: return player.emit('warn', `Unexpected event type: ${message.type}`); } } } @@ -326,21 +321,20 @@ class PlayerManager extends Map { */ async join(guildId, channelId, options, player) { options = options || {}; - player = player || this.get(guildId); - if (player && player.channelId !== channelId) { - player.switchChannel(channelId); - return Promise.resolve(player); - } + return new Promise(async (res, rej) => { + if (player && player.channelId !== channelId) { + player.switchChannel(channelId); + return res(player); + } - const region = this.getRegionFromData(options.region || 'us'); - const node = await this.findIdealNode(region); + const region = this.getRegionFromData(options.region || 'us'); + const node = await this.findIdealNode(region); - if (!node) { - return Promise.reject('No available voice nodes.'); - } + if (!node) { + return rej('No available voice nodes.'); + } - return new Promise((res, rej) => { this.pendingGuilds[guildId] = { channelId: channelId, options: options || {}, @@ -402,7 +396,7 @@ class PlayerManager extends Map { /** * Called by eris when a voice server update is received - * @param {*} data The voice server update from eris + * @param {Object} data The voice server update from eris * @private */ async voiceServerUpdate(data) { @@ -485,7 +479,7 @@ class PlayerManager extends Map { /** * Get ideal region from data * @param {string} endpoint Endpoint or region - * @returns {void} + * @returns {string} * @private */ getRegionFromData(endpoint) { diff --git a/src/index.js b/src/index.js index 1f06595..3faae5e 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,3 @@ -'use strict'; - const Player = require('./Player'); const PlayerManager = require('./PlayerManager'); const Lavalink = require('./Lavalink'); diff --git a/test/app.js b/test/app.js index 64bd22f..5843ce2 100644 --- a/test/app.js +++ b/test/app.js @@ -1,65 +1,59 @@ -const { Client } = require("discord.js"); -const { get } = require("snekfetch"); -const config = require("./config.json"); -const { PlayerManager } = require("../src/index"); +const { Client } = require('discord.js'); +const { get } = require('snekfetch'); +const config = require('./config.json'); +const { PlayerManager } = require('../src/index'); const client = new Client(); const nodes = [ - { host: "localhost", port: 80, region: "asia", password: "youshallnotpass" } + { host: 'localhost', port: 80, region: 'asia', password: 'youshallnotpass' } ]; -client.on("ready", () => { - console.log(`LavaLink testing bot is ready!!!!!!`); - client.player = new PlayerManager(client, nodes, { - numShards: 1, - userId: client.user.id, - defaultRegion: "us" - }); +client.on('ready', () => { + console.log(`LavaLink testing bot is ready!!!!!!`); + client.player = new PlayerManager(client, nodes, { + numShards: 1, + userId: client.user.id, + defaultRegion: 'us' + }); }); -const prefix = "."; -client.on("message", async msg => { - if (!msg.guild) return; - if (!msg.content.startsWith(prefix)) return; - const args = msg.content.slice(prefix.length).trim().split(/ +/g); - const cmd = args.shift().toLowerCase(); - if (cmd === "play") { - if (!msg.member.voiceChannel) return msg.channel.send("You need to be in a voice channel"); - const [song] = await getSongs(`ytsearch:${args.join(" ")}`); - if (!song) return; - client.player.join(msg.guild.id, msg.member.voiceChannel.id).then(async player => { - msg.channel.send(JSON.stringify(song.info), { code: "json" }); - await player.play(song.track, { region: msg.guild.region }); - - player.on("end", async () => { - msg.channel.send("Song has ended... leaving voice channel"); - await player.disconnect(); - }); - }).catch(console.log); - } - - if (cmd === "eval" && msg.author.id === config.owner || "272689325521502208") { - const evaled = await eval(args.join(" ").replace(new RegExp(client.token, "g"), "No u")); - return msg.channel.send(require("util").inspect(evaled, { depth: 0, showHidden: true }), { code: "js", split: true }); - } - +const prefix = '.'; +client.on('message', async msg => { + if (!msg.guild) return; + if (!msg.content.startsWith(prefix)) return; + const args = msg.content.slice(prefix.length).trim().split(/ +/g); + const cmd = args.shift().toLowerCase(); + if (cmd === 'play') { + if (!msg.member.voiceChannel) return msg.channel.send('You need to be in a voice channel'); + const [song] = await getSongs(`ytsearch:${args.join(' ')}`); + if (!song) return; + client.player.join(msg.guild.id, msg.member.voiceChannel.id).then(async player => { + msg.channel.send(JSON.stringify(song.info), { code: 'json' }); + await player.play(song.track, { region: msg.guild.region }); + + player.on('end', async () => { + msg.channel.send('Song has ended... leaving voice channel'); + await player.disconnect(); + }); + }).catch(console.log); + } }); client.login(config.token); -process.on("unhandledRejection", error => console.log(`unhandledRejection:\n${error.stack}`)) - .on("uncaughtException", error => { - console.log(`uncaughtException:\n${error.stack}`); - process.exit(); - }) - .on("error", error => console.log(`Error:\n${error.stack}`)) - .on("warn", error => console.log(`Warning:\n${error.stack}`)); +process.on('unhandledRejection', error => console.log(`unhandledRejection:\n${error.stack}`)) + .on('uncaughtException', error => { + console.log(`uncaughtException:\n${error.stack}`); + process.exit(); + }) + .on('error', error => console.log(`Error:\n${error.stack}`)) + .on('warn', error => console.log(`Warning:\n${error.stack}`)); async function getSongs(search) { - const { body } = await get(`http://localhost:2333/loadtracks?identifier=${search}`) - .set("Authorization", "youshallnotpass") - .set("Accept", "application/json"); - if (!body) return `No tracks found`; - return body; + const { body } = await get(`http://localhost:2333/loadtracks?identifier=${search}`) + .set('Authorization', 'youshallnotpass') + .set('Accept', 'application/json'); + if (!body) return `No tracks found`; + return body; }