Spaces:
Runtime error
Runtime error
| import os from 'node:os' | |
| import { URL } from 'node:url' | |
| import { randomLetters, debugLog, sendResponse, sendResponseNonNull, verifyMethod, encodeTrack, decodeTrack, tryParseBody } from '../utils.js' | |
| import config from '../../config.js' | |
| import sources from '../sources.js' | |
| import VoiceConnection from './voiceHandler.js' | |
| const clients = {} | |
| let statsInterval = null | |
| let playerUpdateInterval = null | |
| function startStats() { | |
| statsInterval = setInterval(() => { | |
| let memoryUsage = process.memoryUsage() | |
| const statistics = { | |
| sent: 0, | |
| nulled: 0, | |
| expected: 0, | |
| deficit: 0 | |
| } | |
| Object.keys(clients).forEach((key) => { | |
| const client = clients[key] | |
| client.players.forEach((player) => { | |
| if (!player.connection) return; | |
| statistics.sent += player.connection.statistics.packetsSent | |
| statistics.nulled += player.connection.statistics.packetsLost | |
| statistics.expected += player.connection.statistics.packetsExpected | |
| }) | |
| }) | |
| statistics.deficit = statistics.sent - statistics.expected | |
| const statisticsResponse = JSON.stringify({ | |
| op: 'stats', | |
| players: nodelinkPlayingPlayersCount, | |
| playingPlayers: nodelinkPlayingPlayersCount, | |
| uptime: Math.floor(process.uptime() * 1000), | |
| memory: { | |
| free: memoryUsage.heapTotal - memoryUsage.heapUsed, | |
| used: memoryUsage.heapUsed, | |
| allocated: 0, | |
| reservable: memoryUsage.rss | |
| }, | |
| cpu: { | |
| cores: os.cpus().length, | |
| systemLoad: os.loadavg()[0], | |
| lavalinkLoad: 0 | |
| }, | |
| frameStats: statistics | |
| }) | |
| Object.keys(clients).forEach((key) => clients[key].ws.send(statisticsResponse, 200)) | |
| }, config.options.statsInterval) | |
| } | |
| function startPlayerUpdate() { | |
| playerUpdateInterval = setInterval(() => { | |
| if (Object.keys(clients).length === 0) return; | |
| Object.keys(clients).forEach((key) => { | |
| const client = clients[key] | |
| client.players.forEach((player) => { | |
| if (!player.connection) return; | |
| player.config.state = { | |
| time: Date.now(), | |
| position: player.connection.playerState.status === 'playing' ? player._getRealTime() : 0, | |
| connected: player.connection.state.status === 'connected', | |
| ping: player.connection.ping || -1 | |
| } | |
| client.ws.send(JSON.stringify({ | |
| op: 'playerUpdate', | |
| guildId: player.guildId, | |
| state: player.config.state | |
| })) | |
| }) | |
| }) | |
| }, config.options.playerUpdateInterval) | |
| } | |
| async function configureConnection(ws, req, parsedClientName) { | |
| let sessionId = null | |
| let client = null | |
| ws.on('close', (code, reason) => { | |
| debugLog('disconnect', 3, { ...parsedClientName, code, reason }) | |
| if (!client) return; | |
| if (clients.length === 1) { | |
| clearInterval(statsInterval) | |
| statsInterval = null | |
| clearInterval(playerUpdateInterval) | |
| playerUpdateInterval = null | |
| if (config.search.sources.youtube && config.options.bypassAgeRestriction) | |
| sources.youtube.free() | |
| } | |
| client.players.forEach((player) => player.destroy()) | |
| delete clients[sessionId] | |
| }) | |
| sessionId = randomLetters(16) | |
| client = { | |
| userId: req.headers['user-id'], | |
| ws, | |
| players: new Map() | |
| } | |
| clients[sessionId] = client | |
| await startSourceAPIs() | |
| ws.send( | |
| JSON.stringify({ | |
| op: 'ready', | |
| resumed: false, | |
| sessionId | |
| }) | |
| ) | |
| } | |
| async function requestHandler(req, res) { | |
| const parsedUrl = new URL(req.url, `http://${req.headers.host}`) | |
| if (config.debug.request.all) { | |
| const body = [] | |
| req.on('data', (chunk) => body.push(chunk)) | |
| req.on('end', () => { | |
| debugLog('all', 6, { method: req.method, path: parsedUrl.pathname, headers: req.headers, body: Buffer.concat(body).toString() }) | |
| req.removeAllListeners() | |
| req.push(Buffer.concat(body)) | |
| }) | |
| } | |
| if (!req.headers || req.headers.authorization !== config.server.password) { | |
| res.writeHead(401, { 'Content-Type': 'text/plain' }) | |
| res.end('Unauthorized') | |
| } | |
| else if (parsedUrl.pathname === '/version') { | |
| if (verifyMethod(parsedUrl, req, res, 'GET')) return; | |
| debugLog('version', 1, { headers: req.headers }) | |
| res.writeHead(200, { 'Content-Type': 'text/plain' }) | |
| res.end(`${config.version.major}.${config.version.minor}.${config.version.patch}${config.version.preRelease ? `-${config.version.preRelease}` : ''}`) | |
| } | |
| else if (parsedUrl.pathname === '/v4/info') { | |
| if (verifyMethod(parsedUrl, req, res, 'GET')) return; | |
| debugLog('info', 1, { headers: req.headers }) | |
| sendResponse(req, res, { | |
| version: { | |
| semver: `${config.version.major}.${config.version.minor}.${config.version.patch}${config.version.preRelease ? `-${config.version.preRelease}` : ''}`, | |
| ...config.version | |
| }, | |
| buildTime: -1, | |
| git: { | |
| branch: 'main', | |
| commit: 'unknown', | |
| commitTime: -1 | |
| }, | |
| nodejs: process.version, | |
| isNodeLink: true, | |
| jvm: '0.0.0', | |
| lavaplayer: '0.0.0', | |
| sourceManagers: Object.keys(config.search.sources).filter((source) => { | |
| if (typeof config.search.sources[source] === 'boolean') return source | |
| return config.search.sources[source].enabled | |
| }), | |
| filters: Object.keys(config.filters.list).filter((filter) => config.filters.list[filter]), | |
| plugins: [] | |
| }, 200) | |
| } | |
| else if (parsedUrl.pathname === '/v4/decodetrack') { | |
| if (verifyMethod(parsedUrl, req, res, 'GET')) return; | |
| let encodedTrack = parsedUrl.searchParams.get('encodedTrack') | |
| if (!encodedTrack) { | |
| debugLog('decodetrack', 1, { params: parsedUrl.pathname, headers: req.headers, error: 'The provided track is invalid.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| error: 'Bad Request', | |
| trace: new Error().stack, | |
| message: 'The provided track is invalid.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| encodedTrack = encodedTrack.replace(/ /, '+') | |
| let decodedTrack = null | |
| if (!encodedTrack || !(decodedTrack = decodeTrack(encodedTrack))) { | |
| debugLog('decodetrack', 1, { params: parsedUrl.pathname, headers: req.headers, error: 'The provided track is invalid.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| error: 'Bad Request', | |
| trace: new Error().stack, | |
| message: 'The provided track is invalid.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| debugLog('decodetrack', 1, { params: parsedUrl.pathname, headers: req.headers }) | |
| sendResponse(req, res, { encoded: encodedTrack, info: decodedTrack }, 200) | |
| } | |
| else if (parsedUrl.pathname === '/v4/decodetracks') { | |
| if (verifyMethod(parsedUrl, req, res, 'POST')) return; | |
| let buffer = '' | |
| if (!(buffer = await tryParseBody(req, res))) return; | |
| if (typeof buffer !== 'object' || !Array.isArray(buffer)) { | |
| debugLog('decodetracks', 1, { headers: req.headers, body: buffer, error: 'The provided body is invalid.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| error: 'Bad request', | |
| trace: new Error().stack, | |
| message: 'The provided body is invalid.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| const tracks = [] | |
| let failed = false | |
| buffer.nForEach((encodedTrack) => { | |
| const decodedTrack = decodeTrack(encodedTrack) | |
| if (!decodedTrack) { | |
| failed = true | |
| debugLog('decodetracks', 1, { headers: req.headers, body: encodedTrack, error: 'The provided track is invalid.' }) | |
| sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| error: 'Bad request', | |
| trace: new Error().stack, | |
| message: 'The provided track is invalid.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| return true | |
| } | |
| tracks.push({ encoded: encodedTrack, info: decodedTrack }) | |
| }) | |
| if (failed) return; | |
| debugLog('decodetracks', 1, { headers: req.headers, body: buffer }) | |
| sendResponse(req, res, tracks, 200) | |
| } | |
| else if (parsedUrl.pathname === '/v4/encodetrack') { | |
| if (verifyMethod(parsedUrl, req, res, 'GET')) return; | |
| let buffer = '' | |
| if (!(buffer = await tryParseBody(req, res))) return; | |
| let encodedTrack = null | |
| if (!(encodedTrack = encodeTrack(buffer))) { | |
| debugLog('encodetrack', 1, { headers: req.headers, body: buffer, error: 'Invalid track object' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| error: 'Bad Request', | |
| trace: new Error().stack, | |
| message: 'Invalid track object', | |
| path: '/v4/encodetrack' | |
| }, 400) | |
| } | |
| debugLog('encodetrack', 1, { headers: req.headers, body: buffer }) | |
| sendResponse(req, res, encodedTrack, 200) | |
| } | |
| else if (parsedUrl.pathname === '/v4/encodetracks') { | |
| if (verifyMethod(parsedUrl, req, res, 'POST')) return; | |
| let buffer = '' | |
| if (!(buffer = await tryParseBody(req, res))) return; | |
| if (typeof buffer !== 'object' || !Array.isArray(buffer)) { | |
| debugLog('decodetracks', 1, { headers: req.headers, body: buffer, error: 'The provided body is invalid.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| error: 'Bad request', | |
| trace: new Error().stack, | |
| message: 'The provided body is invalid.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| const tracks = [] | |
| buffer.forEach((track) => { | |
| let encodedTrack = null | |
| if (!(encodedTrack = encodeTrack(track))) { | |
| debugLog('encodetracks', 1, { headers: req.headers, body: buffer, error: 'Invalid track object' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| error: 'Bad Request', | |
| trace: new Error().stack, | |
| message: 'Invalid track object', | |
| path: '/v4/encodetracks' | |
| }, 400) | |
| } | |
| tracks.push(encodedTrack) | |
| }) | |
| debugLog('encodetracks', 1, { headers: req.headers, body: buffer }) | |
| sendResponse(req, res, tracks, 200) | |
| } | |
| else if (parsedUrl.pathname === '/v4/stats') { | |
| if (verifyMethod(parsedUrl, req, res, 'GET')) return; | |
| debugLog('stats', 1, { headers: req.headers }) | |
| const statistics = { | |
| sent: 0, | |
| nulled: 0, | |
| expected: 0, | |
| deficit: 0 | |
| } | |
| Object.keys(clients).forEach((key) => { | |
| const client = clients[key] | |
| client.players.forEach((player) => { | |
| if (!player.connection) return; | |
| statistics.sent += player.connection.statistics.packetsSent | |
| statistics.nulled += player.connection.statistics.packetsLost | |
| statistics.expected += player.connection.statistics.packetsExpected | |
| }) | |
| }) | |
| statistics.deficit = statistics.sent - statistics.expected | |
| sendResponse(req, res, { | |
| players: nodelinkPlayersCount, | |
| playingPlayers: nodelinkPlayingPlayersCount, | |
| uptime: Math.floor(process.uptime() * 1000), | |
| memory: { | |
| free: process.memoryUsage().heapTotal - process.memoryUsage().heapUsed, | |
| used: process.memoryUsage().heapUsed, | |
| allocated: 0, | |
| reservable: process.memoryUsage().rss | |
| }, | |
| cpu: { | |
| cores: os.cpus().length, | |
| systemLoad: os.loadavg()[0], | |
| lavalinkLoad: 0 | |
| }, | |
| frameStats: statistics | |
| }, 200) | |
| } | |
| else if (parsedUrl.pathname === '/v4/loadtracks') { | |
| if (verifyMethod(parsedUrl, req, res, 'GET')) return; | |
| debugLog('loadtracks', 1, { params: parsedUrl.pathname, headers: req.headers }) | |
| const search = await sources.loadTracks(parsedUrl.searchParams.get('identifier')) | |
| sendResponse(req, res, search, 200) | |
| return; | |
| } | |
| else if (parsedUrl.pathname === '/v4/loadlyrics') { | |
| if (verifyMethod(parsedUrl, req, res, 'GET')) return; | |
| const encodedTrack = parsedUrl.searchParams.get('encodedTrack') | |
| let decodedTrack = null | |
| if (!encodedTrack || !(decodedTrack = decodeTrack(encodedTrack))) { | |
| debugLog('loadlyrics', 1, { params: parsedUrl.pathname, headers: req.headers, error: 'The provided track is invalid.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| error: 'Bad Request', | |
| trace: new Error().stack, | |
| message: 'The provided track is invalid.', | |
| path: '/v4/loadlyrics' | |
| }, 400) | |
| } | |
| const language = parsedUrl.searchParams.get('language') | |
| const captions = await sources.loadLyrics(parsedUrl, req, decodedTrack, language) | |
| debugLog('loadlyrics', 1, { params: parsedUrl.pathname, headers: req.headers }) | |
| sendResponse(req, res, captions, 200) | |
| } | |
| else if (/^\/v4\/sessions\/[A-Za-z0-9]+\/players$(?!\/)/.test(parsedUrl.pathname)) { | |
| if (verifyMethod(parsedUrl, req, res, 'GET')) return; | |
| const client = clients[/^\/v4\/sessions\/([A-Za-z0-9]+)\/players$/.exec(parsedUrl.pathname)[1]] | |
| if (!client) { | |
| debugLog('getPlayers', 1, { params: parsedUrl.pathname, headers: req.headers, error: 'The provided session Id doesn\'t exist.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 404, | |
| trace: new Error().stack, | |
| message: 'The provided session Id doesn\'t exist.', | |
| path: parsedUrl.pathname | |
| }, 404) | |
| } | |
| const players = [] | |
| client.players.forEach((player) => { | |
| player.config.state = { | |
| time: Date.now(), | |
| position: player.connection ? player.connection.playerState.status === 'playing' ? player._getRealTime() : 0 : 0, | |
| connected: player.connection ? player.connection.state.status === 'ready' : false, | |
| ping: player.connection?.ping || -1 | |
| } | |
| players.push(player.config) | |
| }) | |
| debugLog('getPlayers', 1, { headers: req.headers }) | |
| sendResponse(req, res, players, 200) | |
| } | |
| else if (/^\/v4\/sessions\/\w+\/players\/\w+./.test(parsedUrl.pathname)) { | |
| if (![ 'DELETE', 'PATCH', 'GET' ].includes(req.method)) { | |
| sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 405, | |
| error: 'Method Not Allowed', | |
| message: `Request method must be DELETE, PATCH or GET`, | |
| path: parsedUrl.pathname | |
| }, 405) | |
| return; | |
| } | |
| const client = clients[/^\/v4\/sessions\/([A-Za-z0-9]+)\/players\/\d+$/.exec(parsedUrl.pathname)[1]] | |
| if (!client) { | |
| debugLog('updatePlayer', 1, { params: parsedUrl.pathname, headers: req.headers, error: 'The provided session Id doesn\'t exist.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 404, | |
| trace: new Error().stack, | |
| message: 'The provided session Id doesn\'t exist.', | |
| path: parsedUrl.pathname | |
| }, 404) | |
| } | |
| const guildId = /\/players\/(\d+)$/.exec(parsedUrl.pathname)[1] | |
| let player = client.players.get(guildId) | |
| if (req.method === 'DELETE') { | |
| if (!player) { | |
| debugLog('deletePlayer', 1, { params: parsedUrl.pathname, headers: req.headers, error: 'The provided guildId doesn\'t exist.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 404, | |
| trace: new Error().stack, | |
| message: 'The provided guildId doesn\'t exist.', | |
| path: parsedUrl.pathname | |
| }, 404) | |
| } | |
| player.destroy() | |
| client.players.delete(guildId) | |
| debugLog('deletePlayer', 1, { params: parsedUrl.pathname, headers: req.headers }) | |
| return sendResponse(req, res, null, 204) | |
| } | |
| if (req.method === 'GET') { | |
| if (!guildId) { | |
| debugLog('getPlayer', 1, { params: parsedUrl.pathname, headers: req.headers, error: 'Missing guildId parameter.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| trace: new Error().stack, | |
| message: 'Missing guildId parameter.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| let player = client.players.get(guildId) | |
| if (!player) { | |
| player = new VoiceConnection(guildId, client) | |
| client.players.set(guildId, player) | |
| } | |
| player.config.state = { | |
| time: Date.now(), | |
| position: player.connection ? player.connection.playerState.status === 'playing' ? player._getRealTime() : 0 : 0, | |
| connected: player.connection ? player.connection.state.status === 'ready' : false, | |
| ping: player.connection?.ping || -1 | |
| } | |
| debugLog('getPlayer', 1, { params: parsedUrl.pathname, headers: req.headers }) | |
| return sendResponse(req, res, player.config, 200) | |
| } | |
| let buffer = '' | |
| if (!(buffer = await tryParseBody(req, res))) return; | |
| if (req.method === 'PATCH') { | |
| if (!player) player = new VoiceConnection(guildId, client) | |
| if (buffer.voice !== undefined) { | |
| if (!buffer.voice.endpoint || !buffer.voice.token || !buffer.voice.sessionId) { | |
| debugLog('voice', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer, error: `Invalid voice object.` }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| trace: new Error().stack, | |
| message: 'Invalid voice object.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| player.updateVoice(buffer.voice) | |
| if (player.cache.track) { | |
| player.play(player.cache.track, decodeTrack(player.cache.track), false) | |
| player.cache.track = null | |
| } | |
| client.players.set(guildId, player) | |
| debugLog('voice', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer }) | |
| } | |
| /* Deprecated */ | |
| const encodedTrack = buffer.track?.encoded === undefined ? buffer.encodedTrack : buffer.track?.encoded | |
| if (encodedTrack !== undefined) { | |
| if (buffer.encodedTrack !== undefined) /* Deprecated */ | |
| debugLog('encodedTrack', 2, { params: parsedUrl.pathname, headers: req.headers, body: buffer, warning: 'The client is using a deprecated method of play (encodedTrack), deprecated by LavaLink. Report to the client GitHub.' }) | |
| if (encodedTrack === null) { | |
| if (!player.config.track) { | |
| debugLog('stop', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer, error: 'The player is not playing.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| trace: new Error().stack, | |
| message: 'The player is not playing.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| player.stop() | |
| debugLog('stop', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer }) | |
| } else { | |
| const noReplace = parsedUrl.searchParams.get('noReplace') | |
| const decodedTrack = decodeTrack(encodedTrack) | |
| if (!decodedTrack) { | |
| debugLog('play', 1, { track: encodedTrack, exception: { message: 'The provided track is invalid.', severity: 'common', cause: 'Invalid track' } }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| trace: new Error().stack, | |
| message: 'The provided track is invalid.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| if (!player.connection.voiceServer) player.cache.track = encodedTrack | |
| else player.play(encodedTrack, decodedTrack, noReplace === true) | |
| debugLog('play', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer }) | |
| } | |
| client.players.set(guildId, player) | |
| } | |
| if (buffer.track?.userData !== undefined) { | |
| player.config.track = { | |
| ...(player.config.track ? player.config.track : {}), | |
| userData: buffer.userData | |
| } | |
| debugLog('userData', 1, { params: parsedUrl.pathname, params: parsedUrl.pathname, body: buffer }) | |
| } | |
| if (buffer.volume !== undefined) { | |
| if (buffer.volume < 0 || buffer.volume > 1000) { | |
| debugLog('volume', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer, error: 'The volume must be between 0 and 1000.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| trace: new Error().stack, | |
| message: 'The volume must be between 0 and 1000.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| player.volume(buffer.volume) | |
| client.players.set(guildId, player) | |
| debugLog('volume', 1, { params: parsedUrl.pathname, params: parsedUrl.pathname, body: buffer }) | |
| } | |
| if (buffer.paused !== undefined) { | |
| if (typeof buffer.paused !== 'boolean') { | |
| debugLog('pause', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer, error: 'The paused value must be a boolean.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| trace: new Error().stack, | |
| message: 'The paused value must be a boolean.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| if (!player.connection?.ws) { | |
| debugLog('pause', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer, error: 'The player is not connected to a voice server.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| trace: new Error().stack, | |
| message: 'The player is not connected to a voice server.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| player.pause(buffer.paused) | |
| client.players.set(guildId, player) | |
| debugLog('pause', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer }) | |
| } | |
| let filters = {} | |
| if (buffer.filters !== undefined) { | |
| if (typeof buffer.filters !== 'object') { | |
| debugLog('filters', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer, error: 'The filters value must be an object.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| trace: new Error().stack, | |
| message: 'The filters value must be an object.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| filters = buffer.filters | |
| debugLog('filters', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer }) | |
| } | |
| if (buffer.position !== undefined) { | |
| if (typeof buffer.position !== 'number' && buffer.endTime !== null) { | |
| debugLog('seek', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer, error: 'The position value must be a number.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| trace: new Error().stack, | |
| message: 'The position value must be a number.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| filters.seek = buffer.position | |
| debugLog('seek', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer }) | |
| } | |
| if (buffer.endTime !== undefined) { | |
| if (typeof buffer.endTime !== 'number' && buffer.endTime !== null) { | |
| debugLog('endTime', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer, error: 'The endTime value must be a number.' }) | |
| return sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 400, | |
| trace: new Error().stack, | |
| message: 'The endTime value must be a number.', | |
| path: parsedUrl.pathname | |
| }, 400) | |
| } | |
| filters.endTime = buffer.endTime | |
| debugLog('endTime', 1, { params: parsedUrl.pathname, headers: req.headers, body: buffer }) | |
| } | |
| if (Object.keys(filters).length != 0 || JSON.stringify(buffer.filters) === '{}') { | |
| player.filters(filters) | |
| client.players.set(guildId, player) | |
| } | |
| /* Updating player state to ensure it's sending up-to-date data */ | |
| player.config.state = { | |
| time: Date.now(), | |
| position: player.connection ? player.connection.playerState.status === 'playing' ? player._getRealTime() : 0 : 0, | |
| connected: player.connection ? player.connection.state.status === 'ready' : false, | |
| ping: player.connection?.ping || -1 | |
| } | |
| sendResponse(req, res, player.config, 200) | |
| } | |
| } | |
| else { | |
| sendResponse(req, res, { | |
| timestamp: Date.now(), | |
| status: 404, | |
| error: 'Not Found', | |
| trace: new Error().stack, | |
| message: 'The requested route was not found.', | |
| path: parsedUrl.pathname | |
| }, 404) | |
| } | |
| } | |
| function startSourceAPIs() { | |
| if (Object.keys(clients).length !== 1) return; | |
| return new Promise((resolve) => { | |
| const sourcesToInitialize = [] | |
| if (config.search.sources.youtube && config.options.bypassAgeRestriction) | |
| sourcesToInitialize.push(sources.youtube) | |
| if (config.search.sources.spotify.enabled) | |
| sourcesToInitialize.push(sources.spotify) | |
| if (config.search.sources.pandora) | |
| sourcesToInitialize.push(sources.pandora) | |
| if (config.search.sources.deezer.enabled) | |
| sourcesToInitialize.push(sources.deezer) | |
| if (config.search.sources.soundcloud.enabled) | |
| sourcesToInitialize.push(sources.soundcloud) | |
| if (config.options.statsInterval) | |
| startStats() | |
| if (config.options.playerUpdateInterval) | |
| startPlayerUpdate() | |
| if (config.search.sources.musixmatch.enabled) | |
| sources.musixmatch.init() | |
| if (sourcesToInitialize.length === 0) resolve() | |
| let i = 0 | |
| sourcesToInitialize.forEach(async (source) => { | |
| await source.init() | |
| if (++i === sourcesToInitialize.length) resolve() | |
| }) | |
| }) | |
| } | |
| export default { | |
| configureConnection, | |
| requestHandler, | |
| startSourceAPIs | |
| } | |