UNPKG

euralink

Version:

🎵 The most advanced, blazing-fast Lavalink client for Node.js with SponsorBlock, real-time lyrics, 60% less RAM usage, and the ultimate music bot experience.

248 lines (216 loc) • 7.61 kB
const { fetch: undiciFetch, Response } = require("undici"); const { Agent } = require("undici"); const nodeUtil = require("node:util"); class Rest { /** * @param {import('./Euralink').Euralink} eura * @param {Object} options - Node-specific options: host, port, secure, password, sessionId, restVersion */ constructor(eura, options) { this.eura = eura; if (!options.host || !options.port || !options.password) { throw new Error('[Rest.js] Missing required node options: host, port, or password'); } this.url = `http${options.secure ? "s" : ""}://${options.host}:${options.port}`; console.log('[Rest.js] Creating Agent with origin:', this.url); this.sessionId = options.sessionId; this.password = options.password; this.version = options.restVersion || 'v4'; // Create persistent agent for better performance try { this.agent = new Agent({ pipelining: 1, // Enable pipelining for faster requests connections: 100, // Increase connection pool tls: { rejectUnauthorized: false // Allow self-signed certificates }, connect: { timeout: 10000 // Faster timeout }, // Performance optimizations keepAliveTimeout: 60000, // Keep connections alive longer keepAliveMaxTimeout: 300000, // Maximum keep-alive time // HTTP/2 optimizations allowH2: true, // Enable HTTP/2 maxConcurrentStreams: 100, // More concurrent streams // Buffer optimizations bodyTimeout: 30000, // Body timeout headersTimeout: 10000, // Headers timeout }); } catch (error) { this.eura.emit("debug", `Failed to create agent: ${error.message}, falling back to default`); this.agent = null; // Fallback to default fetch } // Request batching for better performance this.pendingRequests = new Map(); this.batchTimeout = null; this.batchDelay = 10; // ms // Cache for frequently accessed data this.cache = new Map(); this.cacheTimeout = 30000; // 30 seconds // Smart cache for track searches this.trackCache = new Map(); this.trackCacheTimeout = 300000; // 5 minutes for track searches // Cache for node info this.nodeInfoCache = new Map(); this.nodeInfoCacheTimeout = 60000; // 1 minute for node info } setSessionId(sessionId) { this.sessionId = sessionId; } // Batch requests for better performance async batchRequest(method, endpoint, body = null, includeHeaders = false) { try { const key = `${method}:${endpoint}:${JSON.stringify(body)}`; if (this.pendingRequests.has(key)) { return this.pendingRequests.get(key); } const promise = this.makeRequest(method, endpoint, body, includeHeaders); this.pendingRequests.set(key, promise); // Clean up after request completes promise.finally(() => { this.pendingRequests.delete(key); }); return promise; } catch (error) { this.eura.emit('restError', error); throw error; } } async makeRequest(method, endpoint, body = null, includeHeaders = false) { const startTime = Date.now(); try { const headers = { 'Authorization': this.password, 'Content-Type': 'application/json', 'User-Agent': `Euralink/${this.version}` }; const requestOptions = { method, headers, body: body ? JSON.stringify(body) : undefined }; if (this.agent) requestOptions.dispatcher = this.agent; const response = await undiciFetch(this.url + endpoint, requestOptions); const responseTime = Date.now() - startTime; this.eura.emit( "debug", `[Rest] ${method} ${endpoint.startsWith("/") ? endpoint : `/${endpoint}`} ${body ? `body: ${JSON.stringify(body)}` : ""} -> Status: ${response.status} (${responseTime}ms)` ); // Handle non-200 responses if (!response.ok) { const errorText = await response.text(); let errorData; try { errorData = JSON.parse(errorText); } catch { errorData = { message: errorText }; } this.eura.emit("debug", `[Rest Error] ${method} ${endpoint} failed: ${response.status} - ${errorData.message || 'Unknown error'}`); throw new Error(`HTTP ${response.status}: ${errorData.message || 'Request failed'}`); } // Parse response once const data = await this.parseResponse(response); // Cache successful GET requests if (method === 'GET' && response.ok) { const cacheKey = `${method}:${endpoint}`; this.cache.set(cacheKey, { data, timestamp: Date.now() }); } return includeHeaders === true ? { data, headers: response.headers, responseTime } : data; } catch (error) { this.eura.emit("debug", `Request failed: ${error.message}`); throw error; } } async parseResponse(response) { try { if (response.status === 204) { return null; } const contentType = response.headers.get("content-type"); if (contentType && contentType.includes("application/json")) { return await response.json(); } else { return await response.text(); } } catch (error) { this.eura.emit("debug", `[Rest - Error] Parse error: ${error.message}`); return null; } } async updatePlayer(options) { const { guildId, data } = options; return this.makeRequest( "PATCH", `/${this.version}/sessions/${this.sessionId}/players/${guildId}`, data ); } async destroyPlayer(guildId) { return this.makeRequest( "DELETE", `/${this.version}/sessions/${this.sessionId}/players/${guildId}` ); } async getPlayers() { return this.makeRequest( "GET", `/${this.version}/sessions/${this.sessionId}/players` ); } async getTracks(identifier) { // Check cache first const cacheKey = `tracks:${identifier}`; const cached = this.trackCache.get(cacheKey); if (cached && (Date.now() - cached.timestamp) < this.trackCacheTimeout) { this.eura.emit("debug", `[Rest Cache] Track cache hit for ${identifier}`); return cached.data; } const result = await this.makeRequest( "GET", `/${this.version}/loadtracks?identifier=${encodeURIComponent(identifier)}` ); // Cache the result this.trackCache.set(cacheKey, { data: result, timestamp: Date.now() }); return result; } async decodeTrack(track) { return this.makeRequest( "GET", `/${this.version}/decodetrack?encodedTrack=${encodeURIComponent(track)}` ); } async decodeTracks(tracks) { return this.makeRequest( "POST", `/${this.version}/decodetracks`, { tracks } ); } async getStats() { return this.makeRequest("GET", `/${this.version}/stats`); } async getInfo() { return this.makeRequest("GET", `/${this.version}/info`); } /** * Clear all REST caches. */ clearAllCaches() { this.cache.clear(); this.trackCache.clear(); this.nodeInfoCache.clear(); this.eura.emit('restCacheCleared'); } } module.exports = Rest;