Chat about this codebase

AI-powered code exploration

Online

Project Overview

DisGo is an idiomatic Go library for building Discord bots and applications. It provides a high-level interface over Discord’s REST and Gateway APIs, plus integrated modules for Webhooks, OAuth2, Voice, caching, and more.

Key Features

  • Full REST API client with builders for messages, embeds, reactions, threads, etc.
  • Gateway support with configurable intents, presence, and sharding
  • Slash commands, components, and modal interactions via built-in HTTP server
  • OAuth2 flows for bot invitations and user authorization
  • Voice connections and audio playback
  • Pluggable cache backend or disable caching entirely
  • Structured event system with both handler interfaces and listener functions
  • Built-in logging (slog) and configurable via bot.WithLogger

When to Use DisGo

  • You need a type-safe, production-ready Go client for Discord
  • You want fine-grained control over Gateway intents and REST rate limits
  • You prefer a modular architecture with easy extension points
  • You value built-in support for interactions (slash commands, buttons, modals)

Project Status

  • Active development with semantic versioning (see SemVersion constant)
  • Requires Go 1.24+
  • Unit-tested core modules; community contributions welcome

Supported Discord Features

  • Gateway Intents (guilds, messages, reactions, voice state, etc.)
  • Slash commands, context menus, message components, modals
  • Thread creation and management
  • Webhooks (create, edit, delete, execute)
  • OAuth2 authorization code and client credentials grants
  • Voice connection lifecycle and audio frames

Licensing

DisGo is licensed under the Apache License 2.0. See the full text in the LICENSE file.
You may use, modify, and distribute DisGo under the terms of Apache 2.0, subject to the conditions outlined in the license.

Getting Help

For detailed guides and advanced patterns, refer to the examples repository and the GoDoc pages.

Getting Started

This guide walks you through installing DisGo, creating a bot token, writing and running a minimal bot, and verifying your setup with build and test commands.

Prerequisites

  • Go 1.24 or newer
  • A Discord application with a Bot user and its token
  • GUILD_MESSAGES intent enabled in the Discord Developer Portal

1. Install DisGo

Initialize your module and add DisGo:

go mod init github.com/yourusername/yourbot
go get github.com/disgoorg/disgo

2. Create and Export Your Bot Token

  1. Open https://discord.com/developers/applications
  2. Select your application → Bot → Copy Token
  3. Export it in your shell:
export BOT_TOKEN="YOUR_BOT_TOKEN_HERE"

3. Write a Minimal Bot

Create main.go:

package main

import (
  "context"
  "log"
  "os"
  "os/signal"
  "syscall"

  "github.com/disgoorg/disgo"
  "github.com/disgoorg/disgo/bot"
  "github.com/disgoorg/disgo/events"
  "github.com/disgoorg/disgo/gateway"
)

func main() {
  // 1. Instantiate client with token and message intent
  client, err := disgo.New(
    os.Getenv("BOT_TOKEN"),
    bot.WithGatewayConfigOpts(
      gateway.WithIntents(gateway.IntentGuildMessages),
    ),
    // 2. Register a simple MessageCreate listener
    bot.WithEventListenerFunc(func(e *events.MessageCreate) {
      if e.Message.Content == "ping" {
        _, _ = e.Client().Rest().CreateMessage(e.ChannelID, "pong")
      }
    }),
  )
  if err != nil {
    log.Fatalf("failed to create client: %v", err)
  }

  // 3. Open gateway connection
  if err := client.OpenGateway(context.Background()); err != nil {
    log.Fatalf("failed to open gateway: %v", err)
  }
  log.Println("Bot is now running")

  // 4. Wait for interrupt signal to shut down gracefully
  stop := make(chan os.Signal, 1)
  signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
  <-stop

  client.CloseGateway(context.Background())
  log.Println("Bot stopped")
}

4. Build and Run

go build -o mybot
./mybot

Your bot logs “Bot is now running” and responds with “pong” whenever it sees “ping” in a guild channel.

5. Verify with Tests

Run DisGo’s test suite and your own code:

go test ./...

6. (Optional) Automate with GitHub Actions

Add .github/workflows/go.yml:

name: Go CI

on:
  push:
    paths:
      - '**/*.go'
      - '**/go.mod'
      - '.github/workflows/go.yml'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with: {go-version: '1.24'}
      - run: go build -v ./...
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with: {go-version: '1.24'}
      - run: go test -v ./...
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-go@v5
        with: {go-version: '1.24'}
      - uses: golangci/golangci-lint-action@v6
        with: {version: latest}

This workflow builds, tests, and lints on each push to keep your bot code healthy.

Core Concepts

Disgo’s architecture centers on a high-level bot.Client that wires together configurable components—gateway, HTTP server, REST, voice, cache, sharding and event handling. Understanding each piece and how they integrate helps you build robust bots.

1. bot.Client

Overview
*bot.Client provides:

  • Gateway/shard lifecycle (OpenGateway, Close)
  • HTTP interaction server (OpenHTTPServer)
  • REST API calls (Rest())
  • Cache access (Cache())
  • Event management (AddEventListeners, EventManager())
  • Shard control (ShardManager(), if enabled)

Key Methods

client, _ := disgo.New(token,
  bot.WithDefaultGateway(),
  bot.WithDefaultShardManager(),
  bot.WithEventListenerFunc(func(e *events.Ready) {
    fmt.Println("Ready as", e.User.Username)
  }),
)
ctx := context.Background()
client.OpenGateway(ctx)        // starts gateway shards
client.OpenHTTPServer()        // starts slash-command server
defer client.Close(ctx)        // clean shutdown

Accessing Subsystems

// Send a message via REST
client.Rest().CreateMessage(channelID, bot.NewCreateMessage(
  bot.MessageCreateContent("Hello"),
))

// Read from cache
member, ok := client.Cache().Members().Get(guildID, userID)

// Find which shard handles a guild
shard, _ := client.ShardManager().ShardForGuild(guildID)

2. Configuration with ConfigOpt

Disgo uses functional options (ConfigOpt) to swap or tune components before building the client.

Common bot options

  • WithDefaultGateway(), WithGatewayConfigOpts(...)
  • WithDefaultShardManager(), WithShardManagerConfigOpts(...)
  • WithRestClientConfigOpts(...), WithCacheConfigOpts(...)
  • WithEventListeners(...), WithEventListenerFunc(...)
  • WithHTTPServerConfigOpts(publicKey, ...)

Example: custom REST timeout and cache flags

client, _ := disgo.New(token,
  bot.WithRestClientConfigOpts(
    rest.WithTimeout(10*time.Second),
  ),
  bot.WithCacheConfigOpts(
    cache.WithCaches(cache.FlagGuilds|cache.FlagMembers),
  ),
)

3. Event Management

EventManager routes gateway and HTTP events to your listeners.

Registering listeners

client.AddEventListeners(
  &events.MessageCreateListener{
    HandleMessageCreate: func(e *events.MessageCreate) {
      log.Println("New message:", e.Message.Content)
    },
  },
)
// or
client.AddEventListenerFunc(func(e *events.GuildMemberAdd) {
  fmt.Println("Member joined:", e.Member.User.Username)
})

Dispatch behavior

  • Listeners run synchronously by default
  • Use bot.WithEventDispatcher(async bool) to toggle async dispatching

4. Cache

cache.NewCache[T any] provides a thread-safe store keyed by snowflake IDs. Disgo pre-builds caches for guilds, members, channels, etc.

Basic usage

// Create a new cache for custom entities
myCache := cache.NewCache[MyType](https://github.com/DisgoOrg/disgo/blob/master/cache.Config{MaxSize: 1000})

// Store and retrieve
myCache.Put(id, entity)
entity, ok := myCache.Get(id)

// Remove or iterate
myCache.Remove(id)
for id, ent := range myCache.All() {
  // process ent
}

Integrate into bot.Client

client, _ := disgo.New(token,
  bot.WithCacheConfigOpts(
    cache.WithCustomCache(cache.FlagChannels, myCache),
  ),
)

5. Gateway

gateway.Gateway manages a single WebSocket connection.

Creating and opening

gw := gateway.New(
  token,
  func(eventType gateway.EventType, sequence int, shardID int, data gateway.EventData) {
    // raw event handling
  },
  func(gw gateway.Gateway, err error) {
    log.Println("Gateway closed:", err)
  },
  gateway.WithIntents(gateway.IntentGuildMessages, gateway.IntentDirectMessages),
  gateway.WithAutoReconnect(true),
)
ctx := context.Background()
gw.Open(ctx)
defer gw.Close()

Key features

  • Heartbeats and resume logic
  • Configurable intents, compression, rate-limiting
  • Raw event hooks via WithEnableRawEvents(true)

6. Sharding

sharding.ShardManager controls multiple gateway shards.

Initialization

sm := shardmanager.New(
  token,
  shardmanager.WithTotalShards(4),
  shardmanager.WithIntents(gateway.IntentGuilds|gateway.IntentGuildMessages),
)
ctx := context.Background()
sm.Open(ctx)    // opens all shards
defer sm.Close()

Runtime controls

// Get shard by guild
shard, _ := sm.ShardForGuild(guildID)

// Re-shard on the fly
sm.Reshard(10)  // scale to 10 shards

// Listen for shard events
sm.AddListenerFunc(func(event shardmanager.ShardEvent) {
  log.Println("Shard", event.ShardID(), "status:", event.Status())
})

Understanding these core building blocks lets you customize connections, handle events efficiently, maintain local state, and scale across Discord’s sharding requirements.

Usage Guides

Practical how-to guides for common tasks with Disgo’s core modules.


Slash Command & Component Routing (handler)

Define declarative routes for slash commands, buttons, modals, and apply middleware.

package main

import (
  "context"
  "log"

  "github.com/disgoorg/disgo"
  "github.com/disgoorg/disgo/events"
  "github.com/disgoorg/disgo/handler"
  "github.com/disgoorg/disgo/handler/middleware"
  "github.com/disgoorg/disgo/discord"
  "github.com/disgoorg/snowflake/v2"
)

func main() {
  token := "Bot YOUR_BOT_TOKEN"
  client, err := disgo.New(token)
  if err != nil {
    log.Fatalf("error creating client: %v", err)
  }

  // Build router
  r := handler.New()
  r.Use(middleware.Logger())       // log all events
  r.NotFound(func(evt *handler.CommandEvent) error {
    return evt.CreateMessage(discord.MessageCreate{Content: "Command not found"})
  })

  // Top-level /ping command
  r.Command("/ping", func(evt *handler.CommandEvent) error {
    return evt.CreateMessage(discord.MessageCreate{Content: "Pong!"})
  })

  // Grouped commands
  r.Group(func(r handler.Router) {
    r.Use(middleware.Print("admin"))
    // /admin ban <user>
    r.Command("/admin/ban", func(evt *handler.CommandEvent) error {
      user := evt.Data.(discord.SlashCommandInteractionData).OptUser("user")
      return evt.CreateMessage(discord.MessageCreate{Content: "Banned " + user.User().Username})
    })
  })

  // Register in a guild (replace with your guild ID)
  guildID := snowflake.ID(123456789012345678)
  if err := handler.SyncCommands(client.Rest(), r.Commands(), []snowflake.ID{guildID}); err != nil {
    log.Fatalf("sync commands: %v", err)
  }

  client.OpenGatewayIntent(context.Background(), disgo.GatewayIntents{
    disgo.IntentGuilds | disgo.IntentGuildMessages | disgo.IntentGuildMessageReactions,
  })
  select {}
}
  1. Build a handler.Router, attach middleware via Use.
  2. Define Command and Component routes with paths.
  3. Call handler.SyncCommands before opening the gateway.
  4. Supply a NotFound fallback for unmatched interactions.

OAuth2 Client & Session Management

Perform Discord OAuth2 flows: generate URLs, obtain tokens, fetch user data.

package main

import (
  "context"
  "encoding/json"
  "fmt"
  "log"
  "net/http"
  "os"

  "github.com/disgoorg/disgo/oauth2"
  "github.com/disgoorg/disgo/discord"
  "github.com/disgoorg/snowflake/v2"
)

var (
  sessions = map[string]*oauth2.Session{}
  oauth    *oauth2.Client
)

func main() {
  clientID := snowflake.GetEnv("CLIENT_ID")
  clientSecret := os.Getenv("CLIENT_SECRET")
  oauth = oauth2.New(clientID, clientSecret)

  http.HandleFunc("/login", handleLogin)
  http.HandleFunc("/callback", handleCallback)
  http.HandleFunc("/", handleRoot)
  log.Fatal(http.ListenAndServe(":8080", nil))
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
  url := oauth.GenerateAuthorizationURL(oauth2.AuthorizationURLParams{
    RedirectURI: "http://localhost:8080/callback",
    Scopes:      []discord.OAuth2Scope{discord.OAuth2ScopeIdentify, discord.OAuth2ScopeEmail},
  })
  http.Redirect(w, r, url, http.StatusFound)
}

func handleCallback(w http.ResponseWriter, r *http.Request) {
  code := r.URL.Query().Get("code")
  state := r.URL.Query().Get("state")
  sess, _, err := oauth.StartSession(code, state)
  if err != nil {
    http.Error(w, "OAuth2 error: "+err.Error(), http.StatusInternalServerError)
    return
  }
  sessions[state] = sess
  http.SetCookie(w, &http.Cookie{Name: "session", Value: state, Path: "/"})
  http.Redirect(w, r, "/", http.StatusFound)
}

func handleRoot(w http.ResponseWriter, r *http.Request) {
  cookie, err := r.Cookie("session")
  if err != nil || sessions[cookie.Value] == nil {
    http.Redirect(w, r, "/login", http.StatusFound)
    return
  }
  sess := sessions[cookie.Value]

  user, err := oauth.GetUser(sess)
  if err != nil {
    http.Error(w, err.Error(), http.StatusInternalServerError)
    return
  }
  data, _ := json.MarshalIndent(user, "", "  ")
  fmt.Fprintf(w, "<pre>%s</pre>", data)
}
  1. Initialize with oauth2.New(clientID, clientSecret).
  2. Redirect users via GenerateAuthorizationURL.
  3. Exchange code for tokens with StartSession.
  4. Store Session (in-memory or in DB).
  5. Call GetUser, GetConnections, etc., using the session.

REST Client: Unified API Access

Instantiate a REST client and call any Discord HTTP endpoint.

package main

import (
  "context"
  "log"

  "github.com/disgoorg/disgo"
  "github.com/disgoorg/disgo/discord"
  "github.com/disgoorg/disgo/rest"
  "github.com/disgoorg/snowflake/v2"
)

func main() {
  token := "Bot YOUR_BOT_TOKEN"
  cli := rest.New(rest.NewClient(token))

  // Fetch channel info
  channelID := snowflake.ID(987654321098765432)
  channel, err := cli.GetChannel(context.Background(), channelID)
  if err != nil {
    log.Fatalf("GetChannel: %v", err)
  }
  log.Printf("Channel: %s (type=%d)", channel.Name, channel.Type)

  // Send a message
  msg, err := cli.CreateMessage(context.Background(), channelID, discord.NewMessageCreateBuilder().
    SetContent("Hello from REST client!").
    Build(),
  )
  if err != nil {
    log.Fatalf("CreateMessage: %v", err)
  }
  log.Printf("Sent message ID: %s", msg.ID)
}
  • Use rest.New(rest.NewClient(token)) for a full-featured REST interface.
  • Pass a context.Context to control cancellations and timeouts.
  • Build requests with builders (e.g. NewMessageCreateBuilder).

Voice Connection Management (voice.Manager)

Manage voice connections per guild, handle updates, create and close connections.

package main

import (
  "context"
  "log"
  "time"

  "github.com/disgoorg/disgo"
  "github.com/disgoorg/disgo/events"
  "github.com/disgoorg/disgo/voice"
  "github.com/disgoorg/snowflake/v2"
)

func main() {
  token := "Bot YOUR_BOT_TOKEN"
  client, _ := disgo.New(token, disgo.WithEventListenerFunc(onVoiceState), disgo.WithEventListenerFunc(onVoiceServer))
  client.OpenGatewayIntent(context.Background(), disgo.GatewayIntents{
    disgo.IntentGuildVoiceStates,
  })
  select {}
}

func onVoiceState(evt *events.VoiceStateUpdate) {
  client := evt.Client().VoiceManager
  client.HandleVoiceStateUpdate(evt)
}

func onVoiceServer(evt *events.VoiceServerUpdate) {
  client := evt.Client().VoiceManager
  client.HandleVoiceServerUpdate(evt)
  guildID := evt.GuildID
  channelID := snowflake.ID(123456789012345678)

  // Create or get existing connection
  conn, err := client.CreateConn(guildID)
  if err != nil {
    log.Println("CreateConn:", err)
    return
  }
  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
  defer cancel()
  if err := conn.Open(ctx, channelID, false, false); err != nil {
    log.Println("Open voice:", err)
    return
  }
  defer conn.Close(ctx)

  // Mark speaking and send a silence frame
  conn.SetSpeaking(ctx, voice.SpeakingFlagMicrophone)
  conn.UDP().Write(voice.SilenceAudioFrame)
  // ... stream or echo audio here ...
}
  1. Listen for VoiceStateUpdate and VoiceServerUpdate events.
  2. Call VoiceManager.HandleVoice… on each event.
  3. Use CreateConn(guildID) to obtain a connection, then Open and Close.
  4. Use SetSpeaking, UDP().Write(...) to send Opus packets.

Webhook Client: Send & Manage Messages

Create a webhook client from ID/token, then send, update, or delete messages.

package main

import (
  "context"
  "log"

  "github.com/disgoorg/disgo"
  "github.com/disgoorg/disgo/discord"
  "github.com/disgoorg/disgo/rest"
  "github.com/disgoorg/disgo/webhook"
  "github.com/disgoorg/snowflake/v2"
)

func main() {
  id := snowflake.GetEnv("WEBHOOK_ID")
  token := "YOUR_WEBHOOK_TOKEN"
  client := webhook.New(id, token)
  defer client.Close(context.Background())

  // Send a message
  msg, err := client.CreateMessage(discord.NewWebhookMessageCreateBuilder().
    SetContent("Hello via webhook").
    Build(),
    rest.CreateWebhookMessageParams{Wait: true},
  )
  if err != nil {
    log.Fatal("CreateMessage:", err)
  }
  log.Println("Sent webhook message ID:", msg.ID)

  // Update the message
  updated, err := client.UpdateMessage(msg.ID, discord.NewWebhookMessageUpdateBuilder().
    SetContent("Edited content").
    Build(),
  )
  if err != nil {
    log.Fatal("UpdateMessage:", err)
  }
  log.Println("Updated content:", updated.Content)

  // Delete the message
  if err := client.DeleteMessage(updated.ID); err != nil {
    log.Fatal("DeleteMessage:", err)
  }
}
  • Use webhook.New(id, token) to instantiate.
  • Always defer client.Close(ctx) to clean up.
  • Pass rest.CreateWebhookMessageParams{Wait: true} to await Discord’s response.
  • Builders simplify payload creation (NewWebhookMessageCreateBuilder, NewWebhookMessageUpdateBuilder).

Advanced Topics

Deep-dive configurations and extensions for production deployments and power users.

Proxy Setup for Gateway & REST Traffic

Define your proxies and disable local rate limiting/compression so external proxies handle those concerns.

Environment Variables

DISGO_TOKEN=YOUR_DISCORD_BOT_TOKEN
DISGO_GUILD_ID=YOUR_GUILD_ID
DISGO_GATEWAY_URL=ws://gateway-proxy:7878
DISGO_REST_URL=http://rest-proxy:7979/api/v10

Initializing the Disgo Client

package main

import (
  "context"
  "log"
  "os"
  "time"

  "github.com/DisgoOrg/disgo/bot"
  "github.com/DisgoOrg/disgo/sharding"
  "github.com/DisgoOrg/disgo/rest"
  "github.com/DisgoOrg/disgo/gateway"
  "github.com/DisgoOrg/disgo/events"
  "github.com/DisgoOrg/disgo/discord"
)

func main() {
  token := os.Getenv("DISGO_TOKEN")
  guildID := discord.Snowflake(os.Getenv("DISGO_GUILD_ID"))
  gatewayURL := os.Getenv("DISGO_GATEWAY_URL")
  restURL := os.Getenv("DISGO_REST_URL")

  client, err := disgo.New(token,
    // Shard manager opts: custom gateway + no compression/rate limiting
    bot.WithShardManagerConfigOpts(
      sharding.WithGatewayConfigOpts(
        gateway.WithURL(gatewayURL),
        gateway.WithCompress(false),
      ),
      sharding.WithRateLimiter(sharding.NewNoopRateLimiter()),
    ),
    // REST client opts: custom REST endpoint, no local rate limiting
    bot.WithRestClientConfigOpts(
      rest.WithURL(restURL),
      rest.WithRateLimiter(rest.NewNoopRateLimiter()),
    ),
    bot.WithEventListenerFunc(commandListener),
  )
  if err != nil {
    log.Fatalf("failed to build Disgo client: %v", err)
  }
  defer client.Close(context.Background())

  // Register slash commands at startup
  commands := []discord.ApplicationCommandCreate{
    discord.SlashCommandCreate{
      Name:        "say",
      Description: "Echoes your message",
      Options: []discord.ApplicationCommandOption{
        discord.ApplicationCommandOptionString{
          Name:        "message",
          Description: "Text to echo",
          Required:    true,
        },
        discord.ApplicationCommandOptionBool{
          Name:        "ephemeral",
          Description: "Ephemeral response?",
          Required:    true,
        },
      },
    },
  }
  if _, err := client.Rest.SetGuildCommands(client.ApplicationID, guildID, commands); err != nil {
    log.Fatalf("failed to register commands: %v", err)
  }

  // Block until shutdown signal
  select {}
}

func commandListener(event *events.ApplicationCommandInteractionCreate) {
  data := event.SlashCommandInteractionData()
  if data.CommandName() != "say" {
    return
  }
  msg := discord.NewMessageCreateBuilder().
    SetContent(data.String("message")).
    SetEphemeral(data.Bool("ephemeral")).
    Build()

  if _, err := event.CreateMessage(msg); err != nil {
    log.Printf("failed to send response: %v", err)
  }
}

Gateway Rate Limiter

Control outgoing gateway commands to stay within Discord’s rate limits.

Creating a Limiter

import (
  "context"
  "log"
  "time"

  "github.com/DisgoOrg/disgo/gateway"
)

limiter := gateway.NewRateLimiter(
  gateway.WithCommandsPerMinute(150),
  gateway.WithLogger(myLogger),
)

Sending Payloads

Wrap each send with Wait/Unlock:

ctx := context.Background()
for _, payload := range payloads {
  if err := limiter.Wait(ctx); err != nil {
    log.Fatalf("rate limiter wait failed: %v", err)
  }

  if err := wsConn.WriteJSON(payload); err != nil {
    limiter.Unlock() // prevent deadlock
    log.Printf("send error: %v", err)
    continue
  }
  limiter.Unlock()
}

Resetting and Shutdown

// On reconnect
limiter.Reset()

// On application shutdown
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := limiter.Close(ctx); err != nil {
  log.Printf("limiter close timeout: %v", err)
}

Internals & Tips

  • Wait sleeps when commands are exhausted until the 1-minute window resets.
  • Unlock decrements the counter and resets the window on first use after expiry.
  • Always pair Wait and Unlock, even on errors.
  • Use Reset when you reestablish the gateway connection.

REST Rate Limiter

Manage HTTP request quotas per-route and globally, unlocking based on Discord’s rate‐limit headers.

Creating a REST Rate Limiter

import (
  "log"
  "time"

  "github.com/DisgoOrg/disgo/rest"
)

restLimiter := rest.NewRateLimiter(
  rest.WithBucketExpiration(15*time.Minute),
  rest.WithCleanupInterval(5*time.Minute),
  rest.WithLogger(myLogger),
)

Injecting into the Client

client, err := disgo.New(token,
  bot.WithRestClientConfigOpts(
    rest.WithRateLimiter(restLimiter),
  ),
)

How It Works

  • Each request acquires a per-route bucket lock before sending.
  • After the response, it reads X-RateLimit-Remaining, X-RateLimit-Reset and unlocks the bucket with the new reset time.
  • Expired buckets are cleaned up periodically to free memory.

Common Usage Pattern

// Example: fetching a channel
ch, err := client.Rest.GetChannel(channelID)
if err != nil {
  log.Printf("failed to fetch channel: %v", err)
}

All REST calls automatically respect locks and unlocks under the hood.

Practical Tips

  • Tune WithBucketExpiration to control memory use for inactive routes.
  • Use WithCleanupInterval to balance CPU vs. memory.
  • Provide a logger to diagnose rate‐limit hits and resets.

Examples Catalog

An index of runnable Disgo samples grouped by theme. Each entry links to a focused example with setup, core logic, and practical tips.

  • Listening to Gateway Events
    Register and handle Discord gateway events via:

    1. Functional listeners
    2. Channel-based listeners
    3. Listener adapters
      Quickly pick the pattern that fits your bot’s architecture.
  • Voice Audio Echo
    Connect a bot to a voice channel, set speaking state, and echo incoming Opus packets.
    Ideal for testing voice latency or learning raw UDP audio handling.

  • Auto Moderation Example
    Programmatically create, update, and delete Auto Moderation rules.
    Subscribe to rule lifecycle and execution events for auditing or notifications.

  • OAuth2 Client: User Authentication and Session Management
    Integrate github.com/disgoorg/disgo/oauth2.Client into an HTTP server.
    Guide covers redirecting users, exchanging codes, persisting sessions via cookies, and fetching user/connection data.

Contributing & Development

This section explains how to report bugs, request features, run CI checks locally, adhere to code style, and submit contributions to DisgoOrg/disgo.

Reporting Bugs

Use our bug report template to provide complete information and speed up triage.

  1. Navigate to Issues > New Issue > Bug Report or open .github/ISSUE_TEMPLATE/bug_report.md
  2. Fill in all sections:
    • Describe the bug: concise summary
    • Error: copy-paste logs or stack traces
    • To Reproduce: minimal Go example
    • Expected behavior: what you expected
    • Version: Disgo version, Go version, OS
    • Additional context: screenshots or links

Example snippet:

**Describe the bug**
Creating an embed with `SetTitle("")` panics.

**Error**
panic: runtime error: invalid memory address

**To Reproduce**
```go
package main

import "github.com/DisgoOrg/disgo/components"

func main() {
    _ = components.NewEmbed().SetTitle("").Build()
}

Expected behavior Empty title yields an error or is ignored.


### Requesting Features

Use our feature request template to propose enhancements.

1. Go to **Issues > New Issue > Feature Request** or open `.github/ISSUE_TEMPLATE/feature_request.md`  
2. Provide:
   - **Problem**: what you’re facing  
   - **Solution**: your proposed API or behavior  
   - **Alternatives**: approaches you considered  
   - **Additional context**: links to docs, related issues  

### Running CI Checks Locally

We mirror GitHub Actions (`.github/workflows/go.yml`) in your workstation:

1. Install Go 1.24  
2. From repo root, run:
   ```bash
   # Format and organize imports
   go fmt ./…
   goimports -w .

   # Run tests with coverage
   go test ./… -coverprofile=coverage.out

   # Lint using golangci-lint (requires .golangci.yml)
   golangci-lint run --timeout 5m
  1. Verify no formatting diffs:
    git diff --exit-code
    

Code Style & Formatting

We enforce consistent style via .editorconfig:

root = true

[*]
charset = utf-8
end_of_line = lf
indent_style = space
indent_size = 4
max_line_length = 180

[*.go]
indent_style = tab
ij_go_run_go_fmt_on_reformat = true
ij_go_import_sorting = gofmt
ij_go_GROUP_CURRENT_PROJECT_IMPORTS = true

[{*.md,*.markdown}]
indent_style = tab
max_line_length = 600

[{*.yaml,*.yml}]
indent_style = space
indent_size = 2

Practical tips:

  • Install EditorConfig plugin in your IDE (VSCode, IntelliJ, Vim).
  • Rely on gofmt/goimports integration for .go files.
  • CI still runs go fmt ./… and golangci-lint run.

Automated Labeling

We auto-label PRs via .github/labeler.yml:

"type:bugfix":
  - head-branch: ['^fix/', '^bugfix/', '^hotfix/']
"type:docs":
  - head-branch: ['^docs/', '^documentation/']
"t:gateway":
  - changed-files:
      - any-glob-to-any-file: ['gateway/**']

Rules:

  • head-branch regex matches your branch name.
  • changed-files globs match paths you modify.

To add a label:

  1. Edit .github/labeler.yml, add a new key matching an existing GitHub label.
  2. Define head-branch patterns and/or changed-files globs.
  3. Commit and open a PR—new rules apply on next PR.

Submitting Contributions

  1. Fork DisgoOrg/disgo and clone your fork.
  2. Create a feature branch:
    git checkout -b feat/my-new-feature
    
  3. Implement your changes, run local CI checks (see above).
  4. Commit with a clear message:
    feat(commands): add support for slash subcommands
    
  5. Push to your fork and open a PR against main:
    • Use the Pull Request template if provided.
    • Reference related issues: Closes #123.
  6. Ensure all CI checks pass before requesting review.

By following these guidelines, you help maintain code quality and streamline collaboration. We appreciate your contributions!