Skip to content

Instantly share code, notes, and snippets.

@Benricheson101
Last active February 26, 2022 09:32
Show Gist options
  • Save Benricheson101/266db2652cc5c6126a4dac1335138629 to your computer and use it in GitHub Desktop.
Save Benricheson101/266db2652cc5c6126a4dac1335138629 to your computer and use it in GitHub Desktop.
Get statistics about your Discord bot. The go file supports large bot sharding
// Copyright (c) 2022 Benjamin Richeson
// 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.
// USAGE INSTRUCTIONS:
// 1. `go mod init owo`
// 2. `go mod tidy`
// 3. `go get`
// 4. `DISCORD_TOKEN='uwu' go run main.go`
package main
import (
"encoding/json"
"net/http"
"os"
"sync"
"sync/atomic"
"time"
tm "github.com/buger/goterm"
"github.com/bwmarrin/discordgo"
)
var (
guilds,
shardsLoaded,
recommendedShards,
maxConcurrency,
memberCount,
partnerCount,
verifiedCount,
verifiedPartnerCount,
gt100k,
gt10k,
gt1k,
largest,
unavailableGuilds int32
)
const GUILD_READY_TIMEOUT time.Duration = time.Second * 20
func main() {
token := os.Getenv("DISCORD_TOKEN")
gwInfo := GetGatewayAuthed(token)
recommendedShards = int32(gwInfo.Shards)
maxConcurrency = int32(gwInfo.SessionStartLimit.MaxConcurrency)
go func() {
for {
updateScreen()
time.Sleep(time.Millisecond * 500)
}
}()
var wg sync.WaitGroup
shardsStarted := 0
for i := 0; i < gwInfo.Shards; i++ {
shardsStarted++
wg.Add(1)
go func(i int) {
defer wg.Done()
done := make(chan bool)
s := createSession(token, i, gwInfo.Shards, done)
s.Open()
<-done
}(i)
if shardsStarted%gwInfo.SessionStartLimit.MaxConcurrency == 0 && shardsStarted != gwInfo.Shards {
time.Sleep(time.Second * 5)
}
}
wg.Wait()
}
func createSession(token string, shardId, shardCount int, done chan<- bool) *discordgo.Session {
s, _ := discordgo.New("Bot " + token)
s.StateEnabled = false
s.Identify.Shard = &[2]int{shardId, shardCount}
s.Identify.Intents = discordgo.IntentsGuilds
var shardGuildCount int32 = 0
var guildsReceived int32 = 0
s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) {
shardGuildCount = int32(len(r.Guilds))
atomic.AddInt32(&guilds, int32(len(r.Guilds)))
atomic.AddInt32(&shardsLoaded, 1)
var wg sync.WaitGroup
wg.Add(1)
go func(shId int) {
defer wg.Done()
time.Sleep(GUILD_READY_TIMEOUT)
g := atomic.LoadInt32(&guildsReceived)
if shardGuildCount-g > 0 {
atomic.AddInt32(&unavailableGuilds, shardGuildCount-g)
s.Close()
done <- true
}
}(shardId)
wg.Wait()
})
s.AddHandler(func(s *discordgo.Session, gc *discordgo.GuildCreate) {
members := int32(gc.MemberCount)
shardGc := atomic.AddInt32(&guildsReceived, 1)
atomic.AddInt32(&memberCount, members)
if members > 100_000 {
atomic.AddInt32(&gt100k, 1)
}
if members > 10_000 {
atomic.AddInt32(&gt10k, 1)
}
if members > 1_000 {
atomic.AddInt32(&gt1k, 1)
}
l := atomic.LoadInt32(&largest)
if members > l {
atomic.StoreInt32(&largest, members)
}
isPartner := strListContains(gc.Features, "PARTNERED")
isVerified := strListContains(gc.Features, "VERIFIED")
if isPartner {
atomic.AddInt32(&partnerCount, 1)
}
if isVerified {
atomic.AddInt32(&verifiedCount, 1)
}
if isPartner && isVerified {
atomic.AddInt32(&verifiedPartnerCount, 1)
}
if shardGc == shardGuildCount {
s.Close()
done <- true
}
})
return s
}
func strListContains(list []string, str string) bool {
for _, s := range list {
if s == str {
return true
}
}
return false
}
func updateScreen() {
tm.Clear()
tm.MoveCursor(1, 1)
guilds := atomic.LoadInt32(&guilds)
tm.Println("Guilds =>", guilds)
tm.Println("Unavailable Guilds =>", atomic.LoadInt32(&unavailableGuilds))
tm.Println("Ready Shards =>", atomic.LoadInt32(&shardsLoaded))
tm.Println("Recommended Shards =>", atomic.LoadInt32(&recommendedShards))
tm.Println("Member Count =>", atomic.LoadInt32(&memberCount))
tm.Println(">= 100,000 =>", atomic.LoadInt32(&gt100k))
tm.Println(">= 10,000 =>", atomic.LoadInt32(&gt10k))
tm.Println(">= 1,000 =>", atomic.LoadInt32(&gt1k))
tm.Println("Partnered =>", atomic.LoadInt32(&partnerCount))
tm.Println("Verified =>", atomic.LoadInt32(&verifiedCount))
tm.Println("P & V =>", atomic.LoadInt32(&verifiedPartnerCount))
tm.Println("Largest =>", atomic.LoadInt32(&largest))
g := guilds
if guilds == 0 {
g = 1
}
tm.Println("Avg Guild Size =>", atomic.LoadInt32(&memberCount)/g)
tm.Flush()
}
type GatewayBot struct {
Shards int `json:"shards"`
URL string `json:"url"`
SessionStartLimit struct {
MaxConcurrency int `json:"max_concurrency"`
Remaining int `json:"Remaining"`
ResetAfter int `json:"reset_after"`
Total int `json:"total"`
} `json:"session_start_limit"`
}
func GetGatewayAuthed(token string) *GatewayBot {
req, _ := http.NewRequest("GET", "https://discord.com/api/v10/gateway/bot", nil)
req.Header.Set("Authorization", "Bot "+token)
res, _ := http.DefaultClient.Do(req)
var body GatewayBot
if err := json.NewDecoder(res.Body).Decode(&body); err != nil {
panic(err)
}
return &body
}
// Copyright (c) 2022 Benjamin Richeson
// 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.
const {Client, LimitedCollection} = require('discord.js');
const client = new Client({
intents: ['GUILDS'],
makeCache: () => new LimitedCollection({maxSize: 0}),
shards: 'auto',
});
let guilds = 0;
let memberCount = 0;
let partnerCount = 0;
let verifiedCount = 0;
let verifiedPartnerCount = 0;
let gt100k = 0;
let gt10k = 0;
let gt1k = 0;
let largest = 0
client.ws.on('GUILD_CREATE', g => {
guilds++;
memberCount += g.member_count;
if (g.member_count >= 100_000) {
gt100k++;
}
if (g.member_count >= 10_000) {
gt10k++;
}
if (g.member_count >= 1_000) {
gt1k++;
}
if (g.member_count > largest) {
largest = g.member_count;
}
const isPartner = g.features.includes("PARTNERED");
const isVerified = g.features.includes("VERIFIED");
if (isPartner) {
partnerCount++;
}
if (isVerified) {
verifiedCount++;
}
if (isPartner && isVerified) {
verifiedPartnerCount++;
}
});
client.on('ready', async () => {
console.log('-- Stats for', client.user.tag, '--')
console.log('Guilds =>', guilds);
console.log('Shards =>', client.ws.shards.size);
console.log('Member Count =>', memberCount.toLocaleString());
console.log('>= 100,000 =>', gt100k.toLocaleString());
console.log('>= 10,000 =>', gt10k.toLocaleString());
console.log('>= 1,000 =>', gt1k.toLocaleString());
console.log('Partnered =>', partnerCount.toLocaleString());
console.log('Verified =>', verifiedCount.toLocaleString());
console.log('P & V =>', verifiedPartnerCount.toLocaleString());
console.log('Largest =>', largest.toLocaleString());
console.log('Avg Guild Size =>', Math.floor(memberCount / guilds));
client.destroy();
process.exit(0);
});
client.login();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment