From a0e543df457120153c15e644633ee48de6bccb78 Mon Sep 17 00:00:00 2001 From: cloudwithax Date: Wed, 15 Mar 2023 20:24:34 -0400 Subject: [PATCH] get very rudimentary base down --- .gitignore | 8 +++ init.lua | 7 ++- libs/node.lua | 157 ++++++++++++++++++++++++++++++++++++++++++++++++ libs/player.lua | 33 ++++++++++ libs/pool.lua | 39 ++++++++++++ libs/utils.lua | 38 ++++++++++++ package.lua | 24 +++++--- 7 files changed, 293 insertions(+), 13 deletions(-) create mode 100644 libs/utils.lua diff --git a/.gitignore b/.gitignore index 6fd0a37..2a4e6c2 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,11 @@ luac.out *.x86_64 *.hex +/deps +test.lua + +*.code-workspace + +*.json +*.log + diff --git a/init.lua b/init.lua index 96f24f2..7bdf71f 100644 --- a/init.lua +++ b/init.lua @@ -1,5 +1,6 @@ return { - node = require("node"), - player = require("player") - pool = require("pool") + Node = require('node'), + Player = require('player'), + Pool = require('pool'), + Utils = require('utils') } diff --git a/libs/node.lua b/libs/node.lua index e69de29..aee1229 100644 --- a/libs/node.lua +++ b/libs/node.lua @@ -0,0 +1,157 @@ +local discordia = require('discordia') +local websocket = require('coro-websocket') +local http = require('coro-http') +local json = require('json') +local querystring = require('querystring') +local utils = require('utils') +local interp = utils.interp +local split = utils.split +local dump = utils.dump + +local Emitter = discordia.Emitter +local class = discordia.class +local Node, get = class('FlareNode', 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', "Flare/1.0.0" } + } + + 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 + print(self._session_id) + elseif payload.op == 'event' then + self:emit('event', payload) + end + elseif data.opcode == 8 then + self:disconnect() + end + end + self:close() +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 = querystring.stringify({ + identifier = _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 + if type(body) == "string" then + return body + else + return json.decode(body) + end + elseif res.code == 204 or method == "DELETE" then + return nil + else + return nil, body + end +end + +return Node diff --git a/libs/player.lua b/libs/player.lua index e69de29..53dc9a3 100644 --- a/libs/player.lua +++ b/libs/player.lua @@ -0,0 +1,33 @@ +local discordia = require('discordia') +local Emitter = discordia.Emitter +local class = discordia.class + +local Player, get = class('FlarePlayer', Emitter) +local format = string.format + +local function bind(t, k) + return function(...) return t[k](t, ...) end +end + +function Player:__init(pool, node, guild, channel) + Emitter.__init(self) + self._pool = pool + self._node = node + self._client = node._client + + self._guild = guild + self._channel = channel + + self._playing = false + self._paused = false + self._volume = 100 + self._track = nil + self._trackPosition = nil + self._lastChecked = nil + self._startedAt = nil + + self._node:on('event', bind(self, '_onEvent')) + self._node:on('killed', bind(self, '_onNodeKilled')) +end + +return Player diff --git a/libs/pool.lua b/libs/pool.lua index e69de29..003ac34 100644 --- a/libs/pool.lua +++ b/libs/pool.lua @@ -0,0 +1,39 @@ +local discordia = require('discordia') +local class = discordia.class + +local Node = require('node') +local Pool, get = class('Pool') + +local format = string.format + + +function Pool:__init() + self._nodes = {} +end + +function Pool:create_node(client, options) + assert(options.identifier, 'Node identifier was not provided.') + if self._nodes[options.identifier] then + return false, + format('Already have node with identifier %s', options.identifier) + end + + options.pool = self + self._nodes[options.identifier] = Node(client, options) +end + +function Pool:get_node(identifier) + if self._nodes[identifier] then + return self._nodes[identifier] + else + return self._nodes[math.random(#self._nodes)] + end +end + +function Pool:disconnect() + for id, node in pairs(self._nodes) do + node.disconnect() + end +end + +return Pool diff --git a/libs/utils.lua b/libs/utils.lua new file mode 100644 index 0000000..2f6c048 --- /dev/null +++ b/libs/utils.lua @@ -0,0 +1,38 @@ +function dump(o) + if type(o) == 'table' then + local s = '{ ' + for k, v in pairs(o) do + if type(k) ~= 'number' then k = '"' .. k .. '"' end + s = s .. '[' .. k .. '] = ' .. dump(v) .. ',' + end + return s .. '} ' + else + return tostring(o) + end +end + +function interp(s, tab) + return (s:gsub('%%%((%a%w*)%)([-0-9%.]*[cdeEfgGiouxXsq])', + function(k, fmt) + return tab[k] and ("%" .. fmt):format(tab[k]) or + '%(' .. k .. ')' .. fmt + end)) +end + +function split(str, character) + local result = {} + + local index = 1 + for s in string.gmatch(str, "[^" .. character .. "]+") do + result[index] = s + index = index + 1 + end + + return result +end + +return { + dump = dump, + interp = interp, + split = split +} diff --git a/package.lua b/package.lua index c609349..e8bc0a4 100644 --- a/package.lua +++ b/package.lua @@ -1,11 +1,15 @@ -return { - name = 'cloudwithax/flare', - version = '1.0.0', - homepage = 'https://github.com/cloudwithax/flare', - license = 'MIT', + return { + name = "flare", + version = "0.0.1", + description = "A fully-featured Lavalink client library for Lua that works seamlessly with Discordia.", + tags = { "discord", "api", "lavalink", "discordia", "lua", "lit", "luvit" }, + license = "MIT", + author = { name = "cloudwithax", email = "heyimnick14@gmail.com" }, + homepage = "https://github.com/cloudwithax/flare", + dependencies = {}, files = { - '**.lua' - }, - tags = { 'discord', 'api', 'lavalink' }, - author = 'cloudwithax', -} + "**.lua", + "!test*" + } + } + \ No newline at end of file