|
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 }) |
|
} |
|
|
|
|
|
const encodedTrack = buffer.track?.encoded === undefined ? buffer.encodedTrack : buffer.track?.encoded |
|
|
|
if (encodedTrack !== undefined) { |
|
if (buffer.encodedTrack !== undefined) |
|
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) |
|
} |
|
|
|
|
|
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 |
|
} |
|
|