UNPKG

lavacord

Version:

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

420 lines (417 loc) 15.5 kB
'use strict'; var Rest_cjs = require('./Rest.cjs'); var cloudstorm = require('cloudstorm'); var index_cjs = require('../index.cjs'); var __defProp = Object.defineProperty; var __name = (target, value) => __defProp(target, "name", { value, configurable: true }); var LavalinkNode = class { /** * Creates a new LavalinkNode instance. * * @summary Initializes a new Lavalink node connection * @param manager - The {@link Manager} instance that controls this node * @param options - Configuration options for this Lavalink node as defined in {@link LavalinkNodeOptions} */ constructor(manager, options) { this.manager = manager; this.id = options.id; if (options.host) Object.defineProperty(this, "host", { value: options.host }); if (options.port) Object.defineProperty(this, "port", { value: options.port }); if (options.password) Object.defineProperty(this, "password", { value: options.password }); if (options.secure !== void 0) Object.defineProperty(this, "secure", { value: options.secure }); if (options.reconnectInterval) this.reconnectInterval = options.reconnectInterval; if (options.sessionId) this.sessionId = options.sessionId; if (options.resuming !== void 0) this.resuming = options.resuming; if (options.resumeTimeout) this.resumeTimeout = options.resumeTimeout; if (options.state) this.state = options.state; } static { __name(this, "LavalinkNode"); } /** * The identifier for this Lavalink node. Used to distinguish between multiple nodes. * * @summary Unique identifier for the node * @remarks * This is a required property that must be unique across all nodes in your application. * It's used for identifying this node in logs and when selecting nodes for new players. */ id; /** * The hostname or IP address of the Lavalink server. * * @summary Server hostname or IP address * @remarks * This can be a domain name, IPv4, or IPv6 address pointing to your Lavalink server. */ host = "localhost"; /** * The port number that the Lavalink server is listening on. * * @summary Server port number * @remarks * This should match the port configured in your Lavalink server's application.yml. */ port = 2333; /** * The time in milliseconds between reconnection attempts if the connection fails. * * @summary Reconnection delay in milliseconds * @remarks * Lower values will attempt reconnections more quickly, but might * cause excessive connection attempts during prolonged server outages. */ reconnectInterval = 1e4; /** * The password used for authorization with the Lavalink server. * * @summary Authorization password for the Lavalink server * @remarks * This password must match the one configured in your Lavalink server's application.yml. * It's used in the Authorization header when establishing the WebSocket connection. */ password = "youshallnotpass"; /** * Whether to use secure connections (HTTPS/WSS) instead of HTTP/WS. * * @summary Secure connection flag for SSL/TLS * @remarks * When true, WebSocket connections will use WSS and REST requests will use HTTPS. * This is required when connecting to Lavalink servers behind SSL/TLS. */ secure = false; /** * The WebSocket instance used for communication with the Lavalink server. * * @summary Active WebSocket connection to the Lavalink server * @remarks * When not connected to Lavalink, this property will be null. * You can check the {@link connected} property to determine connection status. */ ws = null; /** * The statistics received from the Lavalink server. * * @summary Server resource usage and player statistics * @remarks * Contains information about system resource usage, player counts, and audio frame statistics. * This is updated whenever the Lavalink server sends a stats update (typically every minute). * You can use these stats to implement node selection strategies in your application. */ stats = { players: 0, playingPlayers: 0, uptime: 0, memory: { used: 0, free: 0, allocated: 0, reservable: 0 }, cpu: { cores: 0, systemLoad: 0, lavalinkLoad: 0 }, frameStats: { sent: 0, nulled: 0, deficit: 0 } }; /** * Whether this node should attempt to resume the session when reconnecting. * * @summary Session resumption flag * @remarks * When true, the node will try to resume the previous session after a disconnect, * preserving player states and connections. This helps maintain playback during * brief disconnections or node restarts. */ resuming = false; /** * The timeout in seconds after which a disconnected session can no longer be resumed. * * @summary Maximum session resumption timeout in seconds * @remarks * This value is sent to the Lavalink server when configuring session resuming. * After this many seconds of disconnection, the session will be fully closed * and cannot be resumed. */ resumeTimeout = 60; /** * Custom data that can be attached to the node instance. * * @summary Custom application data storage * @remarks * Not used internally by Lavacord, available for application-specific needs. * You can use this property to store any data relevant to your implementation, * such as region information, feature flags, or custom metrics. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any state; /** * The unique session identifier provided by Lavalink on successful connection. * * @summary Lavalink session identifier * @remarks * This ID is used for resuming sessions and in certain REST API calls. * It's automatically assigned when connecting to the Lavalink server. */ sessionId; /** * The version of the Lavalink protocol this node is using. * * @summary Lavalink protocol version * @remarks * This is set automatically when connecting to the Lavalink server. * It indicates which version of the Lavalink protocol this node supports. * The default value is "4", which corresponds to the latest stable version. * * @defaultValue "4" */ version = "4"; /** * Timeout reference used for the reconnection mechanism. * This holds the NodeJS.Timeout instance used to schedule reconnection attempts. */ _reconnect; /** * Current reconnection attempt count for exponential backoff. */ _reconnectAttempts = 0; /** * Tracks whether the session has been updated with the Lavalink server. * Used internally to avoid redundant session update requests. */ _sessionUpdated = false; /** * Establishes a connection to the Lavalink server. * * This method creates a new WebSocket connection to the configured Lavalink server. * If the node is already connected, it will close the existing connection first. * The method sets up event listeners for the WebSocket to handle messages, errors, * and connection state changes. * * Note: This method is primarily used internally by the {@link Manager} class. * Users typically should not call this method directly as the Manager handles * node connections automatically. * * @returns A promise that resolves when connected or rejects if connection fails * @throws {Error} If the connection fails due to network issues, authentication problems, or other errors */ async connect() { this.version = await Rest_cjs.Rest.version(this).then((str) => str.split(".")[0]).catch(() => "4"); const headers = { Authorization: this.password, "User-Id": this.manager.userId, "Client-Name": `Lavacord/${index_cjs.VERSION}` }; if (this.sessionId && this.resuming) headers["Session-Id"] = this.sessionId; if (!this.ws) { this.ws = new cloudstorm.BetterWs(this.socketURL, { headers, encoding: "json", connectThrottle: 0 }); this.ws.on("ws_receive", (data) => this.onMessage(data)).on("error", (e) => this.onError(e)).on("ws_close", (code, reason) => this.onClose(code, reason)); } if (["connecting", "upgrading"].includes(this.ws.sm.currentStateName)) { await cloudstorm.eventSwitch(this.ws, { ws_open: /* @__PURE__ */ __name(() => void 0, "ws_open") }); return this.ws; } if (["connected", "half_close"].includes(this.ws.sm.currentStateName)) await this.ws.close(1e3, "reconnecting"); this.ws.connect(); await cloudstorm.eventSwitch(this.ws, { ws_open: /* @__PURE__ */ __name(() => { this.onOpen(); }, "ws_open"), error: /* @__PURE__ */ __name((e) => { throw e; }, "error"), ws_close: /* @__PURE__ */ __name((code, reason) => { throw new Error(`WebSocket closed during connection: ${code} ${reason.toString()}`); }, "ws_close") }); return this.ws; } /** * Gracefully closes the connection to the Lavalink server. * * This method closes the WebSocket connection with a normal closure code (1000) * and a reason of "destroy", indicating an intentional disconnection rather * than an error condition. * * Note: This method is primarily used internally by the {@link Manager} class. * Users typically should not call this method directly as the Manager handles * node disconnections automatically. * * @returns void */ destroy() { if (!this.connected) return; this.ws.close(1e3, "destroy"); } /** * Indicates whether this node is currently connected to the Lavalink server. * * @summary Connection status check * @remarks * Checks if the {@link ws} instance exists and if its ready state is 1. * This property is useful for verifying connection status before attempting operations * or implementing node selection strategies. * * @returns `true` if connected, `false` otherwise */ get connected() { if (!this.ws) return false; return this.ws.sm.currentStateName === "connected"; } /** * Gets the WebSocket URL for connecting to the Lavalink server. * * @summary WebSocket connection URL * @remarks * Returns either a secure (wss://) or insecure (ws://) WebSocket URL * based on the {@link secure} property configuration. * * @returns The complete WebSocket URL including protocol, host, port, and path */ get socketURL() { const protocol = this.secure ? "wss" : "ws"; return `${protocol}://${this.host}:${this.port}/v${this.version}/websocket`; } /** * Gets the REST API base URL for the Lavalink server. * * @summary REST API base URL * @remarks * Returns either a secure (https://) or insecure (http://) REST URL * based on the {@link secure} property configuration. * * @returns The complete REST API base URL including protocol, host, port, and path */ get restURL() { const protocol = this.secure ? "https" : "http"; return `${protocol}://${this.host}:${this.port}`; } /** * Handles the WebSocket 'open' event when a connection is established. */ onOpen() { if (this._reconnect) clearTimeout(this._reconnect); this._reconnectAttempts = 0; this.manager.emit("ready", this); } /** * Processes incoming WebSocket messages from the Lavalink server. * @param msg - The raw data received from the WebSocket */ onMessage(msg) { switch (msg.op) { case "ready": if (msg.sessionId) this.sessionId = msg.sessionId; if (!this._sessionUpdated) { this._sessionUpdated = true; Rest_cjs.Rest.updateSession(this).catch((e) => this.manager.emit("error", e, this)); } break; case "stats": { const { op, ...stats } = msg; this.stats = stats; break; } case "event": this._handleEvent(msg); break; case "playerUpdate": { const player = this.manager.players.get(msg.guildId); if (!player) break; player.state = msg.state; if (player.listenerCount("state")) player.emit("state", msg.state); if (this.manager.listenerCount("playerState")) this.manager.emit("playerState", player, msg.state); break; } } this.manager.emit("raw", msg, this); } /** * Handles WebSocket errors. * @param error - The error received from the WebSocket */ onError(error) { if (!error) return; this.manager.emit("error", error, this); this.destroy(); } /** * Handles WebSocket closure. * * @param code - The WebSocket close code (see Lavalink API for code meanings) * @param reason - The reason why the WebSocket was closed */ onClose(code, reason) { this._sessionUpdated = false; this.manager.emit("disconnect", code, reason, this); switch (code) { case 1e3: if (reason.toString() === "destroy") return clearTimeout(this._reconnect); break; case 4001: case 4002: case 4003: case 4004: case 4005: this.manager.emit("error", new Error(`Lavalink authentication error: ${code} ${reason}`), this); return; } this.reconnect(); } /** * Initiates a reconnection attempt after a delay with exponential backoff. */ reconnect() { this._reconnectAttempts++; const delay = Math.min(this.reconnectInterval * Math.pow(2, this._reconnectAttempts - 1), 6e4); this._reconnect = setTimeout(async () => { this.manager.emit("reconnecting", this); await this.connect().catch(() => void 0); }, delay); } _handleEvent(data) { const player = this.manager.players.get(data.guildId); if (!player) return; switch (data.type) { case "TrackStartEvent": player.track = data.track; player.timestamp = Date.now(); if (player.listenerCount("trackStart")) player.emit("trackStart", data); if (this.manager.listenerCount("playerTrackStart")) this.manager.emit("playerTrackStart", player, data); break; case "TrackEndEvent": if (data.reason !== "replaced") { player.track = null; player.timestamp = null; } if (player.listenerCount("trackEnd")) player.emit("trackEnd", data); if (this.manager.listenerCount("playerTrackEnd")) this.manager.emit("playerTrackEnd", player, data); break; case "TrackExceptionEvent": if (player.listenerCount("trackException")) player.emit("trackException", data); if (this.manager.listenerCount("playerTrackException")) this.manager.emit("playerTrackException", player, data); break; case "TrackStuckEvent": if (player.listenerCount("trackStuck")) player.emit("trackStuck", data); if (this.manager.listenerCount("playerTrackStuck")) this.manager.emit("playerTrackStuck", player, data); break; case "WebSocketClosedEvent": player.track = null; player.timestamp = null; if (player.listenerCount("webSocketClosed")) player.emit("webSocketClosed", data); if (this.manager.listenerCount("playerWebSocketClosed")) this.manager.emit("playerWebSocketClosed", player, data); break; default: this.manager.emit("warn", `Unexpected event type: ${data.type}`); break; } } }; exports.LavalinkNode = LavalinkNode; //# sourceMappingURL=LavalinkNode.cjs.map //# sourceMappingURL=LavalinkNode.cjs.map