---@diagnostic disable: need-check-nil local discordia = require('discordia') local websocket = require('coro-websocket') local http = require('coro-http') local json = require('json') local utils = require('utils') local enums = require('enums') local interp = utils.interp local split = utils.split local dump = utils.dump local url = require('url') local SearchType = enums.SearchType local Track = require('track') local Playlist = require('playlist') local package = require('../../package') local Emitter = discordia.Emitter local class = discordia.class local Node, get = class('Node', Emitter) local format = string.format getmetatable("").__mod = interp function Node:__init(client, options) Emitter.__init(self) assert(options, 'No options were provided.') self._client = assert(client, 'Discordia Client was not provided.') self._host = assert(options.host, 'Host was not provided.') self._port = options.port or 2333 self._password = options.password or 'youshallnotpass' self._identifier = assert(options.identifier, "Node identifier was not provided.") self._heartbeat = options.heartbeat or 30 self._secure = options.secure or false if self._secure then self._websocket_uri = format('wss://%s:%s/', self._host, self._port) self._rest_uri = format('https://%s:%s/', self._host, self._port) else self._websocket_uri = format('ws://%s:%s/', self._host, self._port) self._rest_uri = format('http://%s:%s/', self._host, self._port) end self._session_id = nil self._available = false self._connected = false self._version = nil self._pool = options.pool self._headers = { { 'Authorization', self._password }, { 'Num-Shards', self._client.shardCount }, { 'User-Id', self._client.user.id }, { 'Client-Name', format("Flare/%s", package.version) } } self._res = nil self._read = nil self._players = {} self:connect() end function Node:_listen() for data in self._read do if data.opcode == 1 then local payload = json.decode(data.payload) -- print(dump(payload)) if payload.op == 'playerUpdate' then self:emit('event', payload) elseif payload.op == 'stats' then payload.op = nil self._stats = payload self:emit('stats', self._stats) elseif payload.op == 'ready' then self._session_id = payload.sessionId elseif payload.op == 'event' then self:emit('event', payload) end elseif data.opcode == 8 then self:disconnect() end end end function Node:connect() if self._connected then return false, 'Already connected' end local options = websocket.parseUrl(self._websocket_uri) options.headers = self._headers local res, read = websocket.connect(options) local version = self:_send('GET', 'version', nil, nil, nil, false) version = split(version, ".") self._version = version[1] if res and res.code == 101 then self._connected = true self._res, self._read = res, read coroutine.wrap(self._listen)(self) print(format("Node with identifier %s has connected successfully.", self._identifier)) return true end return false, read end function Node:disconnect(forced) if not self._connected then return end self._connected = false self._res, self._read = nil, nil if not forced then self:_reconnect() end end function Node:_send(method, path, _guild_id, _query, _data, include_version) if include_version and not self._connected then return end local guild_id = "" local query = _query or "" local data = _data or "" local version = "" if include_version then version = "/v" .. self._version .. "/" end if _guild_id then guild_id = "/" .. _guild_id end if _query then query = "?" .. _query end local uri = format('%s%s%s%s%s', self._rest_uri, version, path, guild_id, query) local res, body = http.request(method, uri, self._headers, data) if res.code == 200 then return json.decode(body) elseif res.code == 204 or method == "DELETE" then return nil else return nil, body end end function Node:get_tracks(_query, search_type) if not self._connected then return end assert(_query, "Query must be provided.") local query = nil if not search_type then local parsed = url.parse(_query) if parsed.host then query = "identifier=" .. utils.escape(_query) else query = "identifier=" .. SearchType.YOUTUBE .. ":" .. utils.escape(_query) end else assert(utils.valueintb(SearchType, search_type), "Search type not valid.") query = "identifier=" .. search_type .. ":" .. utils.escape(_query) end local data = self:_send('GET', 'loadtracks', nil, query) local load_type = data.loadType if not load_type then return print("There was an error while trying to load this track.") end if load_type == "NO_MATCHES" then return nil elseif load_type == "LOAD_FAILED" then local exception = data.exception return print(format("Error loading track: %s [%s]", exception.message, exception.severity)) elseif load_type == "SEARCH_RESULT" or load_type == "TRACK_LOADED" then local tracks = {} for _, v in pairs(data.tracks) do table.insert(tracks, Track(v)) end return tracks elseif load_type == "PLAYLIST_LOADED" then local tracks = {} for _, v in pairs(data.tracks) do table.insert(tracks, Track(v)) end return Playlist(data, tracks) else return print("There was an error while trying to load this track.") end end return Node