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",