diff --git a/.gitignore b/.gitignore index fb7ff62..ef96dbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -deps -.vscode \ No newline at end of file +**/deps/ +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 4d5c629..7e06125 100644 --- a/README.md +++ b/README.md @@ -1,120 +1,123 @@ -# topgg-lua +# Top.gg Lua SDK + +The community-maintained Lua library for Top.gg. + ## Installation -To install this library, place the topgg folder beside your root folder and require it by using this segment of code: + +To install this library, place [the `topgg` directory](https://github.com/top-gg-community/lua-sdk/tree/main/topgg) beside your root directory, then install the following dependencies from the lit repository: + +``` +creationix/coro-http +luvit/json +luvit/secure-socket +luvit/timer +``` + +## Setting up + ```lua -package.path = './?/init.lua' .. package.path -local topgg = require('topgg') +local topgg = require("topgg"); + +local botId = "BOT_ID"; + +topgg.Api:init(os.getenv("TOPGG_TOKEN"), botId); ``` -to ensure that it ran successfully, you can run + +## Usage + +### Getting a bot + ```lua -topgg.test() +local bot = topgg.Api:getBot("264811613708746752"); ``` -## Dependencies -Install the following dependencies from the lit repository: +### Getting several bots + +```lua +local bots = topgg.Api:getBots({ + sort = "date", + limit = 50, + offset = 0 +}); ``` -creationix/coro-http@3.2.0 -luvit/json -luvit/secure-socket + +### Getting your bot's voters + +```lua +-- Page number +local voters = topgg.Api:getVotes(1); ``` -## Using the library -Start using the API component of the library by using +### Check if a user has voted for your bot + ```lua -topgg.Api:init(token, id) +local hasVoted = topgg.Api:hasVoted("661200758510977084"); ``` -## Example usage -Here we use our `isWeekend()` and `getStats()` method as an example. +### Getting your bot's server count + ```lua -local topgg = require('topgg'); -local Api = topgg.Api:init('YOUR-TOP.GG-TOKEN-GOES-HERE', 'YOUR-CLIENT-ID-GOES-HERE'); +local stats = topgg.Api:getStats(); +local serverCount = stats.server_count; +``` -local checkWeekend = coroutine.create(function() - print(topgg.Api:isWeekend()); -end); +### Posting your bot's server count -coroutine.resume(checkWeekend); -- This will print `false` if it's not the weekends but it'll be `true` when it's the weekends. +```lua +topgg.Api:postStats({ + serverCount = bot:getServerCount() +}); +``` + +### Automatically posting your bot's server count every few minutes + +With Discordia: + +```lua +local discordia = require("discordia"); +local client = discordia.Client(); + +client:on('ready', function() + print(client.user.username .. " is now ready!"); + + autoposter = topgg.AutoPoster:init(os.getenv("TOPGG_TOKEN"), client); -local getBotStats = coroutine.create(function(id) - print(topgg.Api:getStats(id)); + autoposter:on("posted", function() + print("Posted stats to Top.gg!"); + end); end); -coroutine.resume(getBotStats, '716061781172158464'); -- This will print a value that can be encoded into a table by using json.decode() +client:run("Bot " .. os.getenv("BOT_TOKEN")); +``` + +### Checking if the weekend vote multiplier is active + +```lua +local isWeekend = topgg.Api:isWeekend(); +``` + +### Generating widget URLs + +#### Large + +```lua +local widgetUrl = topgg.Widget.large("discord_bot", "574652751745777665"); +``` + +#### Votes + +```lua +local widgetUrl = topgg.Widget.votes("discord_bot", "574652751745777665"); ``` -## Documentation (Api) -`Api:init(token, id)` -Params | Type | Required | Description ---- | --- | --- | --- -`token` | `string` | ✅ | Your top.gg token -`id` | `string` | ✅ | Your client ID ---- - -`Api:request(method, path, body, query)` -Params | Type | Required | Description ---- | --- | --- | --- -`method` | `string` | ✅ | The HTTP request method (e.g. `GET`) -`path` | `string` | ✅ | The top.gg endpoint -`body` | `table` | ❌ | The payload to send while making a `POST`, `PATCH` or `PUT` request -`query` | `table` | ❌ | The query for the passed endpoint ---- - -`Api:commit(method, url, req, body)` -Params | Type | Required | Description ---- | --- | --- | --- -`method` | `string` | ✅ | The HTTP request method (e.g. `GET`) -`url` | `string` | ✅ | The URL to make a request to -`req` | `table` | ❌ | The headers of the request -`body` | `table` | ❌ | The payload to send while making a `POST`, `PATCH` or `PUT` request ---- - -`Api:postStats(stats)` -Params | Type | Required | Description ---- | --- | --- | --- -`stats` | `table` | ✅ | The stats object -`stats.serverCount` / `stats.server_count` | `number` | ✅ | The client's server count -`stats.shardId` / `stats.shard_id` | `number` | ❌ | The client's shard ID -`stats.shardCount` / `stats.shard_count` | `number` | ❌ | The client's shard count ---- - -`Api:getStats(id)` -Params | Type | Required | Description ---- | --- | --- | --- -`id` | `string` | ✅ | The ID of the bot to get stats of ---- - -`Api:getBot(id)` -Params | Type | Required | Description ---- | --- | --- | --- -`id` | `string` | ✅ | The ID of the bot to get information of ---- - -`Api:getBots(query)` -Params | Type | Required | Description ---- | --- | --- | --- -`query` | `table` | ❌ | The query object -`query.fields` | `any` | ❌ | The fields of the query -`query.search` | `any` | ❌ | The search query ---- - -`Api:getUser(id)` -Params | Type | Required | Description ---- | --- | --- | --- -`id` | `string` | ✅ | The ID of the user to get information of (top.gg user info) ---- - -`Api:hasVoted(id)` -Params | Type | Required | Description ---- | --- | --- | --- -`id` | `string` | ✅ | The ID of the user to check if they have voted for the bot the `Api` class was invoked with ---- - -`Api:getVotes()`
No params. - ---- - -`Api:isWeekend()`
No params. - -## Contributors -[Voltrex](https://github.com/VoltrexMaster)
[Matthew.](https://github.com/matthewthechickenman)
[MILLION](https://github.com/Million900o)
[null](https://github.com/vierofernando) +#### Owner + +```lua +local widgetUrl = topgg.Widget.owner("discord_bot", "574652751745777665"); +``` + +#### Social + +```lua +local widgetUrl = topgg.Widget.social("discord_bot", "574652751745777665"); +``` \ 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..ef3d64e 100644 --- a/topgg/init.lua +++ b/topgg/init.lua @@ -1,6 +1,7 @@ package.path = './deps/?/init.lua;./deps/?.lua;./topgg/lib/?.lua;./deps/secure-socket/?.lua;' .. package.path; return { Api = require('api'), - Autoposter = require('autoposter'), + AutoPoster = require('autoposter'), + Widget = require('widget'), test = require('test') } \ No newline at end of file diff --git a/topgg/lib/EventEmitter.lua b/topgg/lib/EventEmitter.lua index 1b3eee3..886e1fe 100644 --- a/topgg/lib/EventEmitter.lua +++ b/topgg/lib/EventEmitter.lua @@ -1,9 +1,6 @@ -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'); diff --git a/topgg/lib/api.lua b/topgg/lib/api.lua index 06256af..237477b 100644 --- a/topgg/lib/api.lua +++ b/topgg/lib/api.lua @@ -4,7 +4,7 @@ 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 base_url = 'https://top.gg/api/v1'; local payloadRequired = {PUT = true, PATCH = true, POST = true}; local function parseErrors(ret, errors, key) @@ -26,10 +26,12 @@ end local Api = require('class')('Api'); -function Api:init(token) - if type(token) ~= 'string' then +function Api:init(token, id) + if type(token) ~= 'string' or type(id) ~= 'string' then error("argument 'token' must be a string"); end + + self.id = id; self.token = token; end @@ -48,15 +50,16 @@ function Api:request(method, path, body, query) end local url = base_url .. path; + local index = 0 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)); + local prefix = index == 0 and '?' or '&'; + index = index + 1; + + url = url .. prefix; + url = url .. urlencode(k) .. '=' .. urlencode(v); end - url = concat(url); end local req = { @@ -89,7 +92,7 @@ function Api:commit(method, url, req, body) res[i] = nil; end - local data = res['content-type'] == 'application/json' and json.decode(msg, 1, json.null) or msg; + local data = res['content-type']:find('application/json', 1, true) and json.decode(msg, 1, json.null) or msg; if res.code < 300 then return data, nil; @@ -117,27 +120,21 @@ function Api:postStats(stats) error("'serverCount' must be a number"); end - local __stats = { - server_count = stats.serverCount or stats.server_count, - }; + local server_count = stats.serverCount or stats.server_count; - 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 + if server == 0 then + error("'serverCount' must be non-zero"); end - local _, res = self:request('POST', '/bots/stats', __stats); - return res; -end - -function Api:getStats(id) - if type(id) ~= 'string' then - error("argument 'id' must be a string"); - end + local __stats = { + server_count = server_count, + }; - local stats = self:request('GET', f('/bots/%s/stats', id)); + return self:request('POST', '/bots/stats', __stats); +end - return stats; +function Api:getStats() + return self:request('GET', '/bots/stats'); end function Api:getBot(id) @@ -150,36 +147,32 @@ end function Api:getBots(query) if query then - if type(query.fields) == 'table' then - query.fields = concat(query.fields, ','); + 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'"); 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; + if type(query.limit) == 'number' and query.limit > 500 then + error("argument 'limit' must not exceed 500"); end - end - return self:request('GET', '/bots', query); -end + if type(query.offset) == 'number' and query.offset < 0 then + error("argument 'offset' must be positive"); + end -function Api:getUser(id) - if type(id) ~= 'string' then - error("argument 'id' must be a string"); + if type(query.fields) == 'table' then + query.fields = concat(query.fields, ','); + end end - return self:request('GET', f('/users/%s', id)); + return self:request('GET', '/bots', nil, query); end -function Api:getVotes() - if not self.token then - error('Missing token'); +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', f('/bots/%s/votes?page=%d', self.id, page)); end function Api:hasVoted(id) diff --git a/topgg/lib/autoposter.lua b/topgg/lib/autoposter.lua index 74c6a50..288234d 100644 --- a/topgg/lib/autoposter.lua +++ b/topgg/lib/autoposter.lua @@ -1,28 +1,27 @@ -local timer = require('timer'); -local setInterval = timer.setInterval; local Api = require('api'); local EventEmitter = require('EventEmitter'); +local timer = require('timer'); + +EventEmitter:__init() local AutoPoster = require('class')('AutoPoster', EventEmitter); function AutoPoster:init(apiToken, client) - if not client or not client.guilds or not client.guilds.__len() then + 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"); end - Api:init(apiToken) + Api:init(apiToken, client.user.id) - setInterval(function() + timer.setInterval(900000, 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); + local stats = {serverCount = #client.guilds} + Api:postStats(stats); + self:emit('posted'); + end); + coroutine.resume(poster); - end, 900000); + end); return self; end diff --git a/topgg/lib/widget.lua b/topgg/lib/widget.lua new file mode 100644 index 0000000..f7cf765 --- /dev/null +++ b/topgg/lib/widget.lua @@ -0,0 +1,53 @@ +local base_url = 'https://top.gg/api/v1'; + +local Widget = {}; + +function Widget.large(ty, id) + if type(id) ~= 'string' then + error("argument 'id' must be a string"); + end + + if ty ~= 'discord_bot' and ty ~= 'discord_server' then + error("argument 'ty' must be 'discord_bot' or 'discord_server'"); + end + + return string.format('%s/widgets/large/%s/%s', base_url, ty:gsub('_', '/'), id); +end + +function Widget.votes(ty, id) + if type(id) ~= 'string' then + error("argument 'id' must be a string"); + end + + if ty ~= 'discord_bot' and ty ~= 'discord_server' then + error("argument 'ty' must be 'discord_bot' or 'discord_server'"); + end + + return string.format('%s/widgets/small/votes/%s/%s', base_url, ty:gsub('_', '/'), id); +end + +function Widget.owner(ty, id) + if type(id) ~= 'string' then + error("argument 'id' must be a string"); + end + + if ty ~= 'discord_bot' and ty ~= 'discord_server' then + error("argument 'ty' must be 'discord_bot' or 'discord_server'"); + end + + return string.format('%s/widgets/small/owner/%s/%s', base_url, ty:gsub('_', '/'), id); +end + +function Widget.social(ty, id) + if type(id) ~= 'string' then + error("argument 'id' must be a string"); + end + + if ty ~= 'discord_bot' and ty ~= 'discord_server' then + error("argument 'ty' must be 'discord_bot' or 'discord_server'"); + end + + return string.format('%s/widgets/small/social/%s/%s', base_url, ty:gsub('_', '/'), id); +end + +return Widget \ No newline at end of file diff --git a/topgg/package.lua b/topgg/package.lua index 40914b4..fd7921a 100644 --- a/topgg/package.lua +++ b/topgg/package.lua @@ -1,15 +1,16 @@ return { name = "topgg-lua", - version = "0.0.1", + version = "1.0.0", 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", + 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/secure-socket", + "luvit/timer" }, files = { "**.lua",