flameface's picture
Upload 25 files
b58c6cb verified
import http from 'node:http'
import { URL } from 'node:url'
import connectionHandler from './handler.js'
import inputHandler from './inputHandler.js'
import config from '../../config.js'
import { debugLog, parseClientName } from '../utils.js'
import { WebSocketServer } from '../ws.js'
if (typeof config.server.port !== 'number')
throw new Error('Port must be a number.')
if (typeof config.server.password !== 'string')
throw new Error('Password must be a string.')
if (typeof config.options.threshold !== 'boolean' && typeof config.options.threshold !== 'number')
throw new Error('Threshold must be a boolean or a number.')
if (typeof config.options.playerUpdateInterval !== 'boolean' && typeof config.options.playerUpdateInterval !== 'number')
throw new Error('Player update interval must be a boolean or a number.')
if (typeof config.options.statsInterval !== 'boolean' && typeof config.options.statsInterval !== 'number')
throw new Error('Stats interval must be a boolean or a number.')
if (typeof config.options.maxResultsLength !== 'number')
throw new Error('Max results length must be a number.')
if (typeof config.options.maxAlbumPlaylistLength !== 'number')
throw new Error('Max album playlist length must be a number.')
if (typeof config.options.maxCaptionsLength !== 'number')
throw new Error('Max captions length must be a number.')
if (typeof config.options.bypassAgeRestriction !== 'boolean')
throw new Error('Bypass age restriction must be a boolean.')
if (!['bandcamp', 'deezer', 'soundcloud', 'youtube', 'ytmusic'].includes(config.search.defaultSearchSource))
throw new Error('Default search source must be either "bandcamp", "deezer", "soundcloud", "youtube" or "ytmusic".')
if (config.search.fallbackSearchSource === 'soundcloud')
throw new Error('SoundCloud is not supported as a fallback source.')
if (config.search.sources.spotify.enabled && !config.search.sources.spotify.market)
throw new Error('Spotify is enabled but no market was provided.')
if (config.search.sources.deezer.enabled && config.search.sources.deezer.decryptionKey === 'DISABLED')
throw new Error('Deezer is enabled but no decryption key or API key was provided.')
if (config.search.sources.soundcloud.enabled && config.search.sources.soundcloud.clientId === 'DISABLED')
throw new Error('SoundCloud is enabled but no client ID was provided.')
if (![ 'high', 'medium', 'low', 'lowest' ].includes(config.audio.quality))
throw new Error('Audio quality must be either "high", "medium", "low" or "lowest".')
if (![ 'xsalsa20_poly1305', 'xsalsa20_poly1305_suffix', 'xsalsa20_poly1305_lite' ].includes(config.audio.encryption))
throw new Error('Encryption must be either "xsalsa20_poly1305", "xsalsa20_poly1305_suffix" or "xsalsa20_poly1305_lite".')
if (typeof config.voiceReceive.timeout !== 'number')
throw new Error('Voice receive timeout must be a number.')
if (![ 'opus', 'pcm' ].includes(config.voiceReceive.type))
throw new Error('Voice receive type must be either "opus" or "pcm".')
const server = http.createServer(connectionHandler.requestHandler)
const v4 = new WebSocketServer()
v4.on('/v4/websocket', connectionHandler.configureConnection)
v4.on('/connection/data', inputHandler.setupConnection)
server.on('upgrade', (req, socket, head) => {
const { pathname } = new URL(req.url, `http://${req.headers.host}`)
if (req.headers.authorization !== config.server.password) {
debugLog('disconnect', 3, { name: 'Unknown', version: '0.0.0', code: 401, reason: 'Invalid password' })
req.socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n')
return req.socket.destroy()
}
const parsedClientName = parseClientName(req.headers['client-name'])
if (!parsedClientName) {
debugLog('connect', 1, { name: req.headers['client-name'], error: 'Client-name doesn\'t conform to NAME/VERSION format.' })
req.socket.write('HTTP/1.1 400 Bad Request\r\n\r\n')
return req.socket.destroy()
}
req.clientInfo = parsedClientName
if (pathname === '/v4/websocket') {
debugLog('connect', 3, parsedClientName)
v4.handleUpgrade(req, socket, head, { 'isNodeLink': true }, (ws) => v4.emit('/v4/websocket', ws, req, parsedClientName))
}
if (pathname === '/connection/data') {
if (!req.headers['guild-id'] || !req.headers['user-id']) {
debugLog('connectCD', 1, { ...parsedClientName, error: `"${!req.headers['guild-id'] ? 'guild-id' : 'user-id'}" header not provided.` })
req.socket.write('HTTP/1.1 400 Bad Request\r\n\r\n')
return req.socket.destroy()
}
debugLog('connectCD', 3, { ...parsedClientName, guildId: req.headers['guild-id'] })
v4.handleUpgrade(req, socket, head, {}, (ws) => v4.emit('/connection/data', ws, req, parsedClientName))
}
})
v4.on('error', (err) => {
debugLog('error', 3, { error: err.message })
})
server.on('error', (err) => {
debugLog('http', 1, { error: err.message })
})
server.listen(config.server.port || 2333, () => {
console.log(`[\u001b[32mwebsocket\u001b[37m]: Listening on port \u001b[94m${config.server.port || 2333}\u001b[37m.`)
})