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 | |
} | |