diff --git a/.gitignore b/.gitignore index fb7ff62..4b0be92 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ -deps -.vscode \ No newline at end of file +**/deps/ +.vscode/ +discordia.log +gateway.json \ No newline at end of file diff --git a/rootfile.lua b/rootfile.lua deleted file mode 100644 index 8105f37..0000000 --- a/rootfile.lua +++ /dev/null @@ -1,14 +0,0 @@ -package.path = "./?/init.lua" .. package.path -local topgg = require("topgg") -local json = require("json") -topgg.Api:init( - "", - "" -); - -local postStats = coroutine.create(function() - local res = topgg.Api:isWeekend("265925031131873281") - print(res); -end); - -coroutine.resume(postStats) diff --git a/topgg/init.lua b/topgg/init.lua index 220bc04..e964dfb 100644 --- a/topgg/init.lua +++ b/topgg/init.lua @@ -1,6 +1,9 @@ -package.path = './deps/?/init.lua;./deps/?.lua;./topgg/lib/?.lua;./deps/secure-socket/?.lua;' .. package.path; +package.path = + './deps/?/init.lua;./deps/?.lua;./topgg/lib/?.lua;./deps/secure-socket/?.lua;' .. package.path + return { - Api = require('api'), - Autoposter = require('autoposter'), - test = require('test') -} \ No newline at end of file + Api = require('api'), + test = function() + print('[topgg-lua TEST] Library loaded successfully') + end, +} diff --git a/topgg/lib/EventEmitter.lua b/topgg/lib/EventEmitter.lua deleted file mode 100644 index 1b3eee3..0000000 --- a/topgg/lib/EventEmitter.lua +++ /dev/null @@ -1,135 +0,0 @@ -local timer = require('timer'); - -local wrap, yield = coroutine.wrap, coroutine.yield; -local resume, running = coroutine.resume, coroutine.running; -local insert, remove = table.insert, table.remove; -local setTimeout, clearTimeout = timer.setTimeout, timer.clearTimeout; - -local EventEmitter = require('class')('EventEmitter'); - -function EventEmitter:__init() - self._listeners = {}; -end - -local function new(self, name, listener) - local listeners = self._listeners[name]; - if not listeners then - listeners = {}; - self._listeners[name] = listeners; - end - insert(listeners, listener); - return listener.fn; -end - -function EventEmitter:on(name, fn) - return new(self, name, {fn = fn}); -end - -function EventEmitter:once(name, fn) - return new(self, name, {fn = fn, once = true}); -end - -function EventEmitter:onSync(name, fn) - return new(self, name, {fn = fn, sync = true}); -end - -function EventEmitter:onceSync(name, fn) - return new(self, name, {fn = fn, once = true, sync = true}); -end - -function EventEmitter:emit(name, ...) - local listeners = self._listeners[name]; - if not listeners then return end - for i = 1, #listeners do - local listener = listeners[i]; - if listener then - local fn = listener.fn; - if listener.once then - listeners[i] = false; - end - if listener.sync then - fn(...); - else - wrap(fn)(...); - end - end - end - if listeners._removed then - for i = #listeners, 1, -1 do - if not listeners[i] then - remove(listeners, i); - end - end - if #listeners == 0 then - self._listeners[name] = nil; - end - listeners._removed = nil; - end -end - -function EventEmitter:getListeners(name) - local listeners = self._listeners[name]; - if not listeners then return function() end end - local i = 0; - return function() - while i < #listeners do - i = i + 1; - if listeners[i] then - return listeners[i].fn; - end - end - end -end - -function EventEmitter:getListenerCount(name) - local listeners = self._listeners[name]; - if not listeners then return 0 end - local n = 0; - for _, listener in ipairs(listeners) do - if listener then - n = n + 1; - end - end - return n; -end - -function EventEmitter:removeListener(name, fn) - local listeners = self._listeners[name]; - if not listeners then return end - for i, listener in ipairs(listeners) do - if listener and listener.fn == fn then - listeners[i] = false; - end - end - listeners._removed = true; -end - -function EventEmitter:removeAllListeners(name) - if name then - self._listeners[name] = nil; - else - for k in pairs(self._listeners) do - self._listeners[k] = nil; - end - end -end - -function EventEmitter:waitFor(name, timeout, predicate) - local thread = running(); - local fn; - fn = self:onSync(name, function(...) - if predicate and not predicate(...) then return end - if timeout then - clearTimeout(timeout); - end - self:removeListener(name, fn); - return assert(resume(thread, true, ...)); - end); - timeout = timeout and setTimeout(timeout, function() - self:removeListener(name, fn); - return assert(resume(thread, false)); - end); - return yield(); -end - -return EventEmitter; diff --git a/topgg/lib/api.lua b/topgg/lib/api.lua index 06256af..f71e6d3 100644 --- a/topgg/lib/api.lua +++ b/topgg/lib/api.lua @@ -1,199 +1,291 @@ -local http = require('coro-http'); -local request = http.request; -local json = require('json'); -local f, gsub, byte = string.format, string.gsub, string.byte; -local insert, concat = table.insert, table.concat; -local running = coroutine.running; -local base_url = 'https://top.gg/api'; -local payloadRequired = {PUT = true, PATCH = true, POST = true}; +local http = require('coro-http') +local timer = require('timer') +local json = require('json') +local base64 = require('base64') + +local baseUrl = 'https://top.gg/api' local function parseErrors(ret, errors, key) - for k, v in pairs(errors) do - if k == '_errors' then - for _, err in ipairs(v) do - insert(ret, f('%s in %s : %s', err.code, key or 'payload', err.message)); - end + for k, v in pairs(errors) do + if k == '_errors' then + for _, err in ipairs(v) do + table.insert( + ret, + string.format( + '%s in %s : %s', + err.code or err.status, + key or 'payload', + err.message or err.detail + ) + ) + end + else + if key then + parseErrors( + ret, + v, + string.format( + k:find('^[%a_][%a%d_]*$') and '%s.%s' or tonumber( + k + ) and '%s[%d]' or '%s[%q]', + k, + v + ) + ) else - if key then - parseErrors(ret, v, f(k:find('^[%a_][%a%d_]*$') and '%s.%s' or tonumber(k) and '%s[%d]' or '%s[%q]', k, v)); - else - parseErrors(ret, v, k); - end + parseErrors(ret, v, k) end - end - return concat(ret, '\n\t'); + end + end + + return table.concat(ret, '\n\t') end -local Api = require('class')('Api'); +local function addBase64Padding(data) + data = data:gsub('-', '+'):gsub('_', '/') -function Api:init(token) - if type(token) ~= 'string' then - error("argument 'token' must be a string"); - end - self.token = token; + local rem = #data % 4 + + if rem ~= 0 then + data = data .. string.rep('=', 4 - rem) + end + + return data end -local function tohex(char) - return f('%%%02X', byte(char)); +local function parseToken(token) + local tokenSegments = {} + + for seg in string.gmatch(token, '([^.]+)') do + table.insert(tokenSegments, seg) + end + + if #tokenSegments ~= 3 then + return nil + end + + local tokenData = base64.decode(addBase64Padding(tokenSegments[2])) + local obj = json.decode(tokenData) + + if obj and obj.id then + return obj.id + end + + return nil end local function urlencode(obj) - return (gsub(tostring(obj), '%W', tohex)); -end - -function Api:request(method, path, body, query) - local _, main = running(); - if main then - error('Cannot make HTTP request outside a coroutine', 2); - end - - local url = base_url .. path; - - if query and next(query) then - for k, v in pairs(query) do - insert(url, #url == 1 and '?' or '&'); - insert(url, urlencode(k)); - insert(url, '='); - insert(url, urlencode(v)); - end - url = concat(url); - end - - local req = { - {'Authorization', self.token} - }; - - if payloadRequired[method] then - body = body and json.encode(body) or '{}'; - insert(req, {'Content-Type', 'application/json'}); - insert(req, {'Content-Length', #body}); - end - - local data, err = self:commit(method, url, req, body); - if data then - return data; - else - return nil, err; - end -end - -function Api:commit(method, url, req, body) - local success, res, msg = pcall(request, method, url, req, body); - - if not success then - return nil, res; - end - - for i, v in ipairs(res) do - res[v[1]:lower()] = v[2]; - res[i] = nil; - end - - local data = res['content-type'] == 'application/json' and json.decode(msg, 1, json.null) or msg; - - if res.code < 300 then - return data, nil; - else if type(data) == 'table' then - if data.code and data.message then - msg = f('HTTP Error %i : %s', data.code, data.message); - else - msg = 'HTTP Error'; - end - - if data.errors then - msg = parseErrors({msg}, data.errors); - end - end + return (string.gsub(tostring(obj), '%W', function(char) + return string.format('%%%02X', string.byte(char)) + end)) end - return nil, msg; + +local Api = {} + +Api.__index = Api + +function Api:new(token) + if type(token) ~= 'string' then + error('argument \'token\' must be a string') + end + + local id = parseToken(token) + + if not id then + error('argument \'token\' is not a valid Top.gg API token') + end + + local object = setmetatable({}, self) + + object.token = token + object.id = id + + return object end -function Api:postStats(stats) - if not stats or (not stats.serverCount and not stats.server_count) then - error('Server count missing'); - end +function Api:__request(method, path, body, query) + local _, main = coroutine.running() - if type(stats.serverCount) ~= 'number' and type(stats.server_count) ~= 'number' then - error("'serverCount' must be a number"); - end + if main then + error('Cannot make HTTP request outside of a coroutine', 2) + end - local __stats = { - server_count = stats.serverCount or stats.server_count, - }; + local url = baseUrl .. path + local index = 0 - if (stats.shardId or stats.shard_id) and (stats.shardCount or stats.shard_count) then - __stats.shard_id = stats.shard_id or stats.shardId - __stats.shard_count = stats.shard_count or stats.shardCount - end + if query and next(query) then + for k, v in pairs(query) do + local prefix = index == 0 and '?' or '&' - local _, res = self:request('POST', '/bots/stats', __stats); - return res; + index = index + 1 + url = url .. prefix .. urlencode(k) .. '=' .. urlencode(v) + end + end + + local request = { { 'Authorization', 'Bearer ' .. self.token } } + + if method ~= 'GET' then + body = body and json.encode(body) or '{}' + + table.insert(request, { 'Content-Type', 'application/json' }) + table.insert(request, { 'Content-Length', #body }) + end + + return self:__commit(method, url, request, body) end -function Api:getStats(id) - if type(id) ~= 'string' then - error("argument 'id' must be a string"); - end +function Api:__commit(method, url, request, body) + local success, res, msg = pcall(http.request, method, url, request, body) + + if not success then + return nil, res + end + + for i, v in ipairs(res) do + res[v[1]:lower()] = v[2] + res[i] = nil + end + + local data = + res['content-type']:find('json', 1, true) and json.decode( + msg, + 1, + json.null + ) or msg + + if res.code < 300 then + return data, nil + elseif type(data) == 'table' then + if (data.code or data.status) and (data.message or data.detail) then + msg = string.format('HTTP Error %i : %s', data.code or data.status, data.message or data.detail) + else + msg = 'HTTP Error' + end + + if data.errors then + msg = parseErrors({ msg }, data.errors) + end + end + + return nil, msg +end - local stats = self:request('GET', f('/bots/%s/stats', id)); +function Api:postStats(stats) + if not stats or not (stats.serverCount or stats.server_count) then + error('Server count missing') + end - return stats; + local newStats = { + server_count = stats.serverCount or stats.server_count, + } + + if type(newStats.server_count) ~= 'number' or newStats.server_count <= 0 then + error('\'server_count\' must be a number and non-zero') + end + + local _, res = self:__request('POST', '/bots/stats', newStats) + return res +end + +function Api:getStats(_id) + return self:__request('GET', '/bots/stats') end function Api:getBot(id) - if type(id) ~= 'string' then - error("argument 'id' must be a string"); - end + if type(id) ~= 'string' then + error('argument \'id\' must be a string') + end - return self:request('GET', f('/bots/%s', id)); + return self:__request('GET', string.format('/bots/%s', id)) end function Api:getBots(query) - if query then - if type(query.fields) == 'table' then - query.fields = concat(query.fields, ','); - end - - if type(query.search) == 'table' then - local search = {}; - for k, v in pairs(query.search) do - insert(search, f('%s: %s', k, v)); - end - query.search = search; - end - end - - return self:request('GET', '/bots', query); + if query then + if type( + query.sort + ) == 'string' and query.sort ~= 'monthlyPoints' and query.sort ~= 'id' and query.sort ~= 'date' then + error('argument \'sort\' must be either \'monthlyPoints\', \'id\', or \'date\'') + elseif type(query.limit) == 'number' and query.limit > 500 then + error('argument \'limit\' must not exceed 500') + elseif type(query.offset) == 'number' and query.offset < 0 then + error('argument \'offset\' must be positive') + end + + if type(query.fields) == 'table' then + query.fields = table.concat(query.fields, ',') + end + end + + return self:__request('GET', '/bots', nil, query) end function Api:getUser(id) - if type(id) ~= 'string' then - error("argument 'id' must be a string"); - end - - return self:request('GET', f('/users/%s', id)); + error('getUser() is deprecated since API v0') end -function Api:getVotes() - if not self.token then - error('Missing token'); - end +function Api:getVotes(page) + if type(page) ~= 'number' or page < 1 then + error('argument \'page\' must be a valid number') + end - return self:request('GET', '/bots/votes'); + return self:__request( + 'GET', + string.format('/bots/%s/votes?page=%d', self.id, page) + ) end function Api:hasVoted(id) - if type(id) ~= 'string' then - error("argument 'id' must be a string"); - end - local data = self:request('GET', f('/bots/check?userId=%s', id)); + if type(id) ~= 'string' then + error('argument \'id\' must be a string') + end + + local data, err = self:__request('GET', string.format('/bots/check?userId=%s', id)) - return not not data.voted; + if data then + return data.voted ~= 0, nil + end + + return nil, err end function Api:isWeekend() - local data = self:request('GET', '/weekend'); - return not not data.is_weekend; + local data, err = self:__request('GET', '/weekend') + + if data then + return data.is_weekend, nil + end + + return nil, err +end + +function Api:newAutoposter(client, posted, delay) + if not client or not client.guilds or not client.user or not client.user.id then + error( + 'argument \'client\' must be a discordia/discordia-like client instance' + ) + elseif type(delay) ~= 'number' or delay < 900000 then + delay = 900000 + end + + local id = timer.setInterval(delay, function() + coroutine.resume( + coroutine.create(function() + local serverCount = #client.guilds + + if serverCount ~= 0 then + self:postStats({ server_count = serverCount }) + + if type(posted) == 'function' then + posted(serverCount) + end + end + end) + ) + end) + + return { stop = function() + timer.clearInterval(id) + end } end -return Api; \ No newline at end of file +return Api diff --git a/topgg/lib/autoposter.lua b/topgg/lib/autoposter.lua deleted file mode 100644 index 74c6a50..0000000 --- a/topgg/lib/autoposter.lua +++ /dev/null @@ -1,30 +0,0 @@ -local timer = require('timer'); -local setInterval = timer.setInterval; -local Api = require('api'); -local EventEmitter = require('EventEmitter'); - -local AutoPoster = require('class')('AutoPoster', EventEmitter); - -function AutoPoster:init(apiToken, client) - if not client or not client.guilds or not client.guilds.__len() then - error("argument 'client' must be a discordia/discordia-like client instance"); - end - - Api:init(apiToken) - - setInterval(function() - local poster = coroutine.create(function() - local stats = {serverCount = client.guilds.__len()} - if client.totalShardCount then - stats.shardCount = client.totalShardCount - end - Api:postStats(stats); - self:emit('posted'); - end); - coroutine.resume(poster); - end, 900000); - - return self; -end - -return AutoPoster; diff --git a/topgg/lib/base64.lua b/topgg/lib/base64.lua new file mode 100644 index 0000000..012cd7a --- /dev/null +++ b/topgg/lib/base64.lua @@ -0,0 +1,202 @@ + +--[[ + + base64 -- v1.5.3 public domain Lua base64 encoder/decoder + no warranty implied; use at your own risk + + Needs bit32.extract function. If not present it's implemented using BitOp + or Lua 5.3 native bit operators. For Lua 5.1 fallbacks to pure Lua + implementation inspired by Rici Lake's post: + http://ricilake.blogspot.co.uk/2007/10/iterating-bits-in-lua.html + + author: Ilya Kolbin (iskolbin@gmail.com) + url: github.com/iskolbin/lbase64 + + COMPATIBILITY + + Lua 5.1+, LuaJIT + + LICENSE + + See end of file for license information. + +--]] + + +local base64 = {} + +local extract = _G.bit32 and _G.bit32.extract -- Lua 5.2/Lua 5.3 in compatibility mode +if not extract then + if _G.bit then -- LuaJIT + local shl, shr, band = _G.bit.lshift, _G.bit.rshift, _G.bit.band + extract = function( v, from, width ) + return band( shr( v, from ), shl( 1, width ) - 1 ) + end + elseif _G._VERSION == "Lua 5.1" then + extract = function( v, from, width ) + local w = 0 + local flag = 2^from + for i = 0, width-1 do + local flag2 = flag + flag + if v % flag2 >= flag then + w = w + 2^i + end + flag = flag2 + end + return w + end + else -- Lua 5.3+ + extract = load[[return function( v, from, width ) + return ( v >> from ) & ((1 << width) - 1) + end]]() + end +end + + +function base64.makeencoder( s62, s63, spad ) + local encoder = {} + for b64code, char in pairs{[0]='A','B','C','D','E','F','G','H','I','J', + 'K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y', + 'Z','a','b','c','d','e','f','g','h','i','j','k','l','m','n', + 'o','p','q','r','s','t','u','v','w','x','y','z','0','1','2', + '3','4','5','6','7','8','9',s62 or '+',s63 or'/',spad or'='} do + encoder[b64code] = char:byte() + end + return encoder +end + +function base64.makedecoder( s62, s63, spad ) + local decoder = {} + for b64code, charcode in pairs( base64.makeencoder( s62, s63, spad )) do + decoder[charcode] = b64code + end + return decoder +end + +local DEFAULT_ENCODER = base64.makeencoder() +local DEFAULT_DECODER = base64.makedecoder() + +local char, concat = string.char, table.concat + +function base64.encode( str, encoder, usecaching ) + encoder = encoder or DEFAULT_ENCODER + local t, k, n = {}, 1, #str + local lastn = n % 3 + local cache = {} + for i = 1, n-lastn, 3 do + local a, b, c = str:byte( i, i+2 ) + local v = a*0x10000 + b*0x100 + c + local s + if usecaching then + s = cache[v] + if not s then + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + cache[v] = s + end + else + s = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[extract(v,0,6)]) + end + t[k] = s + k = k + 1 + end + if lastn == 2 then + local a, b = str:byte( n-1, n ) + local v = a*0x10000 + b*0x100 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[extract(v,6,6)], encoder[64]) + elseif lastn == 1 then + local v = str:byte( n )*0x10000 + t[k] = char(encoder[extract(v,18,6)], encoder[extract(v,12,6)], encoder[64], encoder[64]) + end + return concat( t ) +end + +function base64.decode( b64, decoder, usecaching ) + decoder = decoder or DEFAULT_DECODER + local pattern = '[^%w%+%/%=]' + if decoder then + local s62, s63 + for charcode, b64code in pairs( decoder ) do + if b64code == 62 then s62 = charcode + elseif b64code == 63 then s63 = charcode + end + end + pattern = ('[^%%w%%%s%%%s%%=]'):format( char(s62), char(s63) ) + end + b64 = b64:gsub( pattern, '' ) + local cache = usecaching and {} + local t, k = {}, 1 + local n = #b64 + local padding = b64:sub(-2) == '==' and 2 or b64:sub(-1) == '=' and 1 or 0 + for i = 1, padding > 0 and n-4 or n, 4 do + local a, b, c, d = b64:byte( i, i+3 ) + local s + if usecaching then + local v0 = a*0x1000000 + b*0x10000 + c*0x100 + d + s = cache[v0] + if not s then + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + cache[v0] = s + end + else + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + decoder[d] + s = char( extract(v,16,8), extract(v,8,8), extract(v,0,8)) + end + t[k] = s + k = k + 1 + end + if padding == 1 then + local a, b, c = b64:byte( n-3, n-1 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + decoder[c]*0x40 + t[k] = char( extract(v,16,8), extract(v,8,8)) + elseif padding == 2 then + local a, b = b64:byte( n-3, n-2 ) + local v = decoder[a]*0x40000 + decoder[b]*0x1000 + t[k] = char( extract(v,16,8)) + end + return concat( t ) +end + +return base64 + +--[[ +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2018 Ilya Kolbin +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +--]] diff --git a/topgg/lib/class.lua b/topgg/lib/class.lua deleted file mode 100644 index fb68d4b..0000000 --- a/topgg/lib/class.lua +++ /dev/null @@ -1,164 +0,0 @@ -local format = string.format -local meta = {} -local names = {} -local classes = {} -local objects = setmetatable({}, {__mode = 'k'}) - -function meta:__call(...) - local obj = setmetatable({}, self) objects[obj] = true - obj:__init(...) - return obj -end - -function meta:__tostring() - return 'class ' .. self.__name -end - -local default = {} - -function default:__tostring() - return self.__name -end - -function default:__hash() - return self -end - -local function isClass(cls) - return classes[cls] -end - -local function isObject(obj) - return objects[obj] -end - -local function isSubclass(sub, cls) - if isClass(sub) and isClass(cls) then - if sub == cls then - return true - else - for _, base in ipairs(sub.__bases) do - if isSubclass(base, cls) then - return true - end - end - end - end - return false -end - -local function isInstance(obj, cls) - return isObject(obj) and isSubclass(obj.__class, cls) -end - -local function profile() - local ret = setmetatable({}, {__index = function() return 0 end}) - - for obj in pairs(objects) do - local name = obj.__name - ret[name] = ret[name] + 1 - end - - return ret -end - -local types = {['string'] = true, ['number'] = true, ['boolean'] = true} - -local function _getPrimitive(v) - return types[type(v)] and v or v ~= nil and tostring(v) or nil -end - -local function serialize(obj) - if isObject(obj) then - local ret = {} - - for k, v in pairs(obj.__getters) do - ret[k] = _getPrimitive(v(obj)) - end - - return ret - else - return _getPrimitive(obj) - end -end - -local rawtype = type - -local function type(obj) - return isObject(obj) and obj.__name or rawtype(obj) -end - -return setmetatable({ - classes = names, - isClass = isClass, - isObject = isObject, - isSubclass = isSubclass, - isInstance = isInstance, - type = type, - profile = profile, - serialize = serialize, -}, {__call = function(_, name, ...) - if names[name] then return error(format('Class %q already defined', name)) end - - local class = setmetatable({}, meta) - classes[class] = true - - for k, v in pairs(default) do - class[k] = v - end - - local bases = {...} - local getters = {} - local setters = {} - - for _, base in ipairs(bases) do - for k1, v1 in pairs(base) do - class[k1] = v1 - - for k2, v2 in pairs(base.__getters) do - getters[k2] = v2 - end - - for k2, v2 in pairs(base.__setters) do - setters[k2] = v2 - end - end - end - - class.__name = name - class.__class = class - class.__bases = bases - class.__getters = getters - class.__setters = setters - local pool = {} - local n = #pool - - function class:__index(k) - if getters[k] then - return getters[k](self) - elseif pool[k] then - return rawget(self, pool[k]) - else - return class[k] - end - end - - function class:__newindex(k, v) - if setters[k] then - return setters[k](self, v) - elseif class[k] or getters[k] then - return error(format('Cannot overwrite protected property: %s.%s', name, k)) - elseif k:find('_', 1, true) ~= 1 then - return error(format('Cannot write property to object without leading underscore: %s.%s', name, k)) - else - if not pool[k] then - n = n + 1 - pool[k] = n - end - return rawset(self, pool[k], v) - end - end - - names[name] = class - return class, getters, setters -end}) \ No newline at end of file diff --git a/topgg/lib/test.lua b/topgg/lib/test.lua deleted file mode 100644 index 2190d52..0000000 --- a/topgg/lib/test.lua +++ /dev/null @@ -1,3 +0,0 @@ -return function () - print("[topgg-lua TEST] Library was loaded successfully") -end diff --git a/topgg/package.lua b/topgg/package.lua index 40914b4..831b581 100644 --- a/topgg/package.lua +++ b/topgg/package.lua @@ -1,19 +1,20 @@ - return { - name = "topgg-lua", - version = "0.0.1", - description = "A library for top.gg, in lua", - tags = { "dbl", "topgg", "top.gg" }, - license = "MIT", - author = { name = "matthewthechickenman", email = "65732060+matthewthechickenman@users.noreply.github.com" }, - homepage = "https://github.com/matthewthechickenman/topgg-lua", - dependencies = { - "creationix/coro-http", - "luvit/json", - "luvit/secure-socket" - }, - files = { - "**.lua", - "!test*" - } - } - \ No newline at end of file +return { + name = 'topgg-lua', + version = '0.1.0', + description = 'A library for top.gg, in lua', + tags = { 'dbl', 'topgg', 'top.gg' }, + license = 'MIT', + author = { + name = 'matthew-st', + email = '65732060+matthewthechickenman@users.noreply.github.com', + }, + homepage = 'https://github.com/Top-gg-Community/lua-sdk', + dependencies = { + 'creationix/coro-http', + 'luvit/json', + 'luvit/secure-socket', + 'luvit/timer', + 'luvit/tls', + }, + files = { '**.lua', '!test*' }, +}