UNPKG

lavacord

Version:

A simple by design lavalink wrapper made for any discord library.

269 lines 9.94 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Manager = void 0; const events_1 = require("events"); const LavalinkNode_1 = require("./LavalinkNode"); const Player_1 = require("./Player"); /** * The class that handles everything to do with Lavalink. it is the hub of the library basically */ class Manager extends events_1.EventEmitter { /** * A [**Map**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) of Lavalink Nodes */ nodes = new Map(); /** * A [**Map**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) of all the players */ players = new Map(); /** * A [**Map**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) of all the VOICE_SERVER_UPDATE States */ voiceServers = new Map(); /** * A [**Map**](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) of all the VOICE_STATE_UPDATE States */ voiceStates = new Map(); /** * The user id of the bot this Manager is managing */ user; /** * The send function needs for the library to function */ send; /** * The Player the manager will use when creating new Players */ Player = Player_1.Player; /** * An Set of all the expecting connections guild id's */ expecting = new Set(); /** * The constructor of the Manager * @param nodes A Array of {@link LavalinkNodeOptions} that the Manager will connect to * @param options The options for the Manager {@link ManagerOptions} */ constructor(nodes, options) { super(); if (options.user) this.user = options.user; if (options.player) this.Player = options.player; if (options.send) this.send = options.send; for (const node of nodes) this.createNode(node); setImmediate(() => { if (!this.send) this.send = () => undefined; }); } /** * Connects all the {@link LavalinkNode} to the respective Lavalink instance */ connect() { if (!this.user) { console.warn("Lavacord Manager.connect was called without the client user ID being set.\ You should construct your Manager when your client becomes ready as that's where you/your Discord lib receives the current user info.\ Alternatively, you can get your client's user ID (which is all that lavacord needs) by doing\ Buffer.from(token.split(\".\")[0], \"base64\").toString(\"utf8\") unless your token was generated a REALLY long time ago."); } return Promise.all(Array.from(this.nodes.values()).map(node => node.connect())); } /** * Disconnects everything, basically destorying the manager. * Stops all players, leaves all voice channels then disconnects all LavalinkNodes */ disconnect() { const promises = []; for (const id of Array.from(this.players.keys())) promises.push(this.leave(id)); for (const node of Array.from(this.nodes.values())) node.destroy(); return Promise.all(promises); } /** * Creating a {@link LavalinkNode} and adds it to the the nodes Map * @param options The node options of the node you're creating */ createNode(options) { const node = new LavalinkNode_1.LavalinkNode(this, options); this.nodes.set(options.id, node); return node; } /** * Disconnects and Deletes the specified node * @param id The id of the node you want to remove */ removeNode(id) { const node = this.nodes.get(id); if (!node) return false; return node.destroy() && this.nodes.delete(id); } /** * Connects the bot to the selected voice channel * @param data The Join Data * @param joinOptions Selfmute and Selfdeaf options, if you want the bot to be deafen or muted upon joining */ async join(data, joinOptions = {}) { const player = this.players.get(data.guild); if (player) return player; await this.sendWS(data.guild, data.channel, joinOptions); return this.spawnPlayer(data); } /** * Leaves the specified voice channel * @param guild The guild you want the bot to leave the voice channel of */ async leave(guild) { await this.sendWS(guild, null); const player = this.players.get(guild); if (!player) return false; if (player.listenerCount("end") && player.playing) { player.emit("end", { track: { encoded: player.track, info: { identifier: "PLACEHOLDER", isSeekable: false, author: "PLACEHOLDER", length: 0, isStream: false, position: 0, title: "PLACEHOLDER", uri: "PLACEHOLDER", artworkUrl: null, isrc: null, sourceName: "PLACEHOLDER" }, pluginInfo: {}, userData: {} }, op: "event", type: "TrackEndEvent", reason: "cleanup", guildId: guild }); } player.removeAllListeners(); await player.destroy(); return this.players.delete(guild); } /** * Switch a player from one node to another, this is to implement fallback * @param player The player you want to switch nodes with * @param node The node you want to switch to */ async switch(player, node) { const { track, state, voiceUpdateState } = { ...player }; const position = (state.position ?? 0) + 2000; if (!voiceUpdateState) { player.node = node; return player; } await player.destroy(); player.node = node; await player.play(track, { position, volume: state.filters.volume ?? 1.0, filters: state.filters, voice: { token: voiceUpdateState.event.token, endpoint: voiceUpdateState.event.endpoint, sessionId: voiceUpdateState.sessionId } }); return player; } /** * For handling voiceServerUpdate from the user's library of choice * @param data The data directly from discord */ voiceServerUpdate(data) { this.voiceServers.set(data.guild_id, data); this.expecting.add(data.guild_id); return this._attemptConnection(data.guild_id); } /** * For handling voiceStateUpdate from the user's library of choice * @param data The data directly from discord */ voiceStateUpdate(data) { if (data.user_id !== this.user) return Promise.resolve(false); if (data.channel_id) { this.voiceStates.set(data.guild_id, data); return this._attemptConnection(data.guild_id); } this.voiceServers.delete(data.guild_id); this.voiceStates.delete(data.guild_id); return Promise.resolve(false); } /** * Just a utility method to easily send OPCode 4 websocket events to discord * @param guild The guild is * @param channel Voice channel id, or null to leave a voice channel * @param param2 Selfmute and Selfdeaf options, if you want the bot to be deafen or muted upon joining */ sendWS(guild, channel, { selfmute = false, selfdeaf = false } = {}) { return this.send({ op: 4, d: { guild_id: guild, channel_id: channel, self_mute: selfmute, self_deaf: selfdeaf } }); } /** * Gets all connected nodes, sorts them by cou load of the node */ get idealNodes() { return Array.from(this.nodes.values()) .filter(node => node.connected) .sort((a, b) => { const aload = a.stats.cpu ? a.stats.cpu.systemLoad / a.stats.cpu.cores * 100 : 0; const bload = b.stats.cpu ? b.stats.cpu.systemLoad / b.stats.cpu.cores * 100 : 0; return aload - bload; }); } /** * Handles the data of voiceServerUpdate & voiceStateUpdate to see if a connection is possible with the data we have and if it is then make the connection to lavalink * @param guildId The guild id that we're trying to attempt to connect to */ async _attemptConnection(guildID) { const server = this.voiceServers.get(guildID); const state = this.voiceStates.get(guildID); if (!server || !state || !this.expecting.has(guildID)) return false; const player = this.players.get(guildID); if (!player) return false; await player.connect({ sessionId: state.session_id, event: server }); this.expecting.delete(guildID); return true; } /** * This creates the {@link Player} * @param data The Join Data, this is called by {@link Manager.join} */ spawnPlayer(data) { const exists = this.players.get(data.guild); if (exists) return exists; const node = this.nodes.get(data.node); if (!node) throw new Error(`INVALID_HOST: No available node with ${data.node}`); const player = new this.Player(node, data.guild); this.players.set(data.guild, player); return player; } } exports.Manager = Manager; //# sourceMappingURL=Manager.js.map