Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018 RumbleFrog
Copyright (c) 2018-2026 RumbleFrog & Top.gg

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
184 changes: 87 additions & 97 deletions bots.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,41 @@ package dbl
import (
"bytes"
"encoding/json"
"fmt"
"strconv"
"strings"
"time"
)

type Reviews struct {
// The project's average review score out of 5
Score float64 `json:"averageScore"`

// The project's review count
Count int `json:"count"`
}

type Bot struct {
// The id of the bot
// The Top.gg id of the bot
ID string `json:"id"`

// The Discord id of the bot
ClientID string `json:"clientid"`

// The username of the bot
Username string `json:"username"`

// The discriminator of the bot
Discriminator string `json:"discriminator"`
Discriminator string `json:"-"`

// The avatar hash of the bot's avatar (may be empty)
// The bot's avatar url
Avatar string `json:"avatar"`

// The cdn hash of the bot's avatar if the bot has none
DefAvatar string `json:"defAvatar"`
DefAvatar string `json:"-"`

// The library of the bot
Library string `json:"lib"`
Library string `json:"-"`

// The prefix of the bot
Prefix string `json:"prefix"`
Expand All @@ -42,7 +54,7 @@ type Bot struct {
// The website url of the bot (may be empty)
Website string `json:"website"`

// The support server invite code of the bot (may be empty)
// The support server url of the bot (may be empty)
Support string `json:"support"`

// The link to the github repo of the bot (may be empty)
Expand All @@ -54,32 +66,32 @@ type Bot struct {
// The custom bot invite url of the bot (may be empty)
Invite string `json:"invite"`

// The date when the bot was approved
// The date when the bot was submitted
Date time.Time `json:"date"`

// The certified status of the bot
CertifiedBot bool `json:"certifiedBot"`

// The vanity url of the bot (deprecated) (may be empty)
// The vanity url of the bot (may be empty)
Vanity string `json:"vanity"`

// The monthly amount of upvotes the bot has (undocumented)
// The monthly amount of votes the bot has
MonthlyPoints int `json:"monthlyPoints"`

// The amount of upvotes the bot has
// The amount of votes the bot has
Points int `json:"points"`

// The GuildID for the donate bot (undocumented) (may be empty)
DonateBotGuildID string `json:"donatebotguildid"`
DonateBotGuildID string `json:"-"`

// The amount of servers the bot is in (undocumented)
// The amount of servers the bot is in
ServerCount int `json:"server_count"`

// Server affiliation ("Servers this bot is in" field) (undocumented)
GuildAffiliation []string `json:"guilds"`
GuildAffiliation []string `json:"-"`

// The amount of servers the bot is in per shard. Always present but can be empty (undocumented)
Shards []int `json:"shards"`
Shards []int `json:"-"`

// The bot's reviews
Review *Reviews `json:"reviews"`
}

type GetBotsPayload struct {
Expand All @@ -94,7 +106,7 @@ type GetBotsPayload struct {
// Field search filter
Search map[string]string

// The field to sort by. Prefix with "-" to reverse the order
// The field to sort by descending, valid field names are "id", "date", and "monthlyPoints".
Sort string

// A list of fields to show
Expand Down Expand Up @@ -135,6 +147,9 @@ type checkResponse struct {
}

type BotStatsPayload struct {
// The amount of servers the bot is in, must not be zero.
ServerCount int `json:"server_count"`

// The amount of servers the bot is in per shard.
Shards []int `json:"shards"`

Expand All @@ -149,39 +164,38 @@ type BotStatsPayload struct {
//
// Use nil if no option is passed
func (c *Client) GetBots(filter *GetBotsPayload) (*GetBotsResult, error) {
if c.token == "" {
return nil, ErrRequireAuthentication
}

if !c.limiter.Allow() {
return nil, ErrLocalRatelimit
}

req, err := c.createRequest("GET", "bots", nil)

if filter != nil {
if err != nil {
return nil, err
} else if filter != nil {
q := req.URL.Query()

if filter.Limit != 0 {
q.Add("limit", strconv.Itoa(filter.Limit))
}
if filter.Limit > 0 {
if filter.Limit > 500 {
filter.Limit = 500
}

if filter.Offset != 0 {
q.Add("offset", strconv.Itoa(filter.Offset))
q.Add("limit", strconv.Itoa(filter.Limit))
} else {
q.Add("limit", "50")
}

if len(filter.Search) != 0 {
tStack := make([]string, 0)

for f, v := range filter.Search {
tStack = append(tStack, f+": "+v)
if filter.Offset >= 0 {
if filter.Offset > 499 {
filter.Offset = 499
}

q.Add("search", strings.Join(tStack, " "))
q.Add("offset", strconv.Itoa(filter.Offset))
}

if filter.Sort != "" {
q.Add("sort", filter.Sort)
switch filter.Sort {
case "id", "date", "monthlyPoints":
q.Add("sort", filter.Sort)
default:
return nil, ErrInvalidRequest
}
}

if len(filter.Fields) != 0 {
Expand Down Expand Up @@ -216,14 +230,6 @@ func (c *Client) GetBots(filter *GetBotsPayload) (*GetBotsResult, error) {

// Information about a specific bot
func (c *Client) GetBot(botID string) (*Bot, error) {
if c.token == "" {
return nil, ErrRequireAuthentication
}

if !c.limiter.Allow() {
return nil, ErrLocalRatelimit
}

req, err := c.createRequest("GET", "bots/"+botID, nil)

if err != nil {
Expand Down Expand Up @@ -253,26 +259,26 @@ func (c *Client) GetBot(botID string) (*Bot, error) {
return bot, nil
}

// Use this endpoint to see who have upvoted your bot
// Fetches your project's recent 100 unique voters
//
// Requires authentication
//
// IF YOU HAVE OVER 1000 VOTES PER MONTH YOU HAVE TO USE THE WEBHOOKS AND CAN NOT USE THIS
func (c *Client) GetVotes(botID string) ([]*User, error) {
if c.token == "" {
return nil, ErrRequireAuthentication
}

if !c.limiter.Allow() {
return nil, ErrLocalRatelimit
// # Requires authentication
func (c *Client) GetVotes(page int) ([]*User, error) {
if page <= 0 {
return nil, ErrInvalidRequest
}

req, err := c.createRequest("GET", "bots/"+botID+"/votes", nil)
req, err := c.createRequest("GET", fmt.Sprintf("bots/%s/votes", c.id), nil)

if err != nil {
return nil, err
}

q := req.URL.Query()

q.Add("page", strconv.Itoa(page))

req.URL.RawQuery = q.Encode()

res, err := c.httpClient.Do(req)

if err != nil {
Expand All @@ -287,7 +293,7 @@ func (c *Client) GetVotes(botID string) ([]*User, error) {

users := make([]*User, 0)

err = json.Unmarshal(body, users)
err = json.Unmarshal(body, &users)

if err != nil {
return nil, err
Expand All @@ -296,19 +302,13 @@ func (c *Client) GetVotes(botID string) ([]*User, error) {
return users, nil
}

// Use this endpoint to see who have upvoted your bot in the past 24 hours. It is safe to use this even if you have over 1k votes.
// Use this endpoint to see who have upvoted for your project in the past 12 hours. It is safe to use this even if you have over 1k votes.
//
// Requires authentication
func (c *Client) HasUserVoted(botID, userID string) (bool, error) {
if c.token == "" {
return false, ErrRequireAuthentication
}

if !c.limiter.Allow() {
return false, ErrLocalRatelimit
}

req, err := c.createRequest("GET", "bots/"+botID+"/check", nil)
// # Requires authentication
//
// The _botID argument is no longer used.
func (c *Client) HasUserVoted(_botID, userID string) (bool, error) {
req, err := c.createRequest("GET", "bots/check", nil)

if err != nil {
return false, err
Expand Down Expand Up @@ -344,16 +344,10 @@ func (c *Client) HasUserVoted(botID, userID string) (bool, error) {
}

// Information about a specific bot's stats
func (c *Client) GetBotStats(botID string) (*BotStats, error) {
if c.token == "" {
return nil, ErrRequireAuthentication
}

if !c.limiter.Allow() {
return nil, ErrLocalRatelimit
}

req, err := c.createRequest("GET", "bots/"+botID+"/stats", nil)
//
// The _botID argument is no longer used.
func (c *Client) GetBotStats(_botID string) (*BotStats, error) {
req, err := c.createRequest("GET", "bots/stats", nil)

if err != nil {
return nil, err
Expand Down Expand Up @@ -382,35 +376,31 @@ func (c *Client) GetBotStats(botID string) (*BotStats, error) {
return botStats, nil
}

// Post your bot's stats
// Post your bot's server count
//
// Requires authentication
// # Requires authentication
//
// If your bot is unsharded, pass in server count as the only item in the slice
func (c *Client) PostBotStats(botID string, payload *BotStatsPayload) error {
if c.token == "" {
return ErrRequireAuthentication
// The _botID argument is no longer used.
func (c *Client) PostBotStats(_botID string, payload *BotStatsPayload) error {
if payload.ServerCount <= 0 {
return ErrInvalidRequest
}

if !c.limiter.Allow() {
return ErrLocalRatelimit
}

encoded, err := json.Marshal(payload)
encoded, err := json.Marshal(&BotStats{
ServerCount: payload.ServerCount,
Shards: []int{},
})

if err != nil {
return err
}

req, err := c.createRequest("POST", "bots/"+botID+"/stats", bytes.NewBuffer(encoded))
req, err := c.createRequest("POST", "bots/stats", bytes.NewBuffer(encoded))

if err != nil {
return err
}

req.Header.Set("Authorization", c.token)
req.Header.Set("Content-Type", "application/json")

res, err := c.httpClient.Do(req)

if err != nil {
Expand Down
9 changes: 7 additions & 2 deletions bots_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dbl

import (
"log"
"os"
"testing"

Expand All @@ -13,14 +14,18 @@ const (
)

func TestBots(t *testing.T) {
client, err := NewClient(os.Getenv("apikey"))
client, err := NewClient(os.Getenv("TOPGG_TOKEN"))

assert.Nil(t, err, "Client should be created w/o error")

bots, err := client.GetBots(&GetBotsPayload{
Limit: fetchLimit,
})

if err != nil {
log.Fatal(err)
}

assert.Nil(t, err, "Request should be successful (API depended)")

assert.Equal(t, fetchLimit, len(bots.Results), "Results array size should match request limit")
Expand All @@ -29,7 +34,7 @@ func TestBots(t *testing.T) {
}

func TestBot(t *testing.T) {
client, err := NewClient(os.Getenv("apikey"))
client, err := NewClient(os.Getenv("TOPGG_TOKEN"))

assert.Nil(t, err, "Client should be created w/o error")

Expand Down
Loading