arikawa

repository·v3·Indexed 20 days ago

https://github.com/diamondburned/arikawa

A modular Golang library for interacting with the Discord API, supporting both bot and user accounts (via ningen). It features independent API client and WebSocket Gateway packages, strictly separated models, pluggable caches, and typed snowflakes. The library provides multiple abstraction layers including session, state, bot, and voice, and includes CLI tools like genevent and gensnowflake for code generation.

Tokens
4.6K
Snippets
17
Records
21
Agent score
69%

What's inside arikawa

  1. Overview of arikawa library features

    v3

    arikawa is a modular Golang library for the Discord API. Key architectural features include:

    • Modular Components: The API client and Websocket Gateway are independent packages.
    • Model Separation: API models and Gateway models are strictly separated to prevent confusion.
    • Event Interception: You can extend and intercept Gateway events (e.g., for reading deleted messages).
    • Pluggable Cache: Supports custom Gateway cache implementations (like Redis) with automatic fallback to the API.
    • Typed Snowflakes: Uses specific types for IDs (e.g., Channel ID vs Message ID) to prevent accidental misuse.
    • User Account Support: Includes support for user accounts via the ningen package (use with caution as self-botting violates Discord's ToS).
  2. How the PreHandler feature works in the state library

    v3
    The state library provides a PreHandler feature. When a PreHandler is used, it calls all registered handlers separately from the session before the state is updated. This is useful for intercepting events (like message deletions) to perform actions before the internal cache reflects the change.
  3. How the Voice connection flow works

    v3

    Connecting to a Discord voice channel involves a multi-step handshake between the application, the library, the Discord Gateway, and the Voice Server:

    1. Initialization: The application calls NewVoice() to get a *Voice instance.
    2. Joining: The application calls JoinChannel(). This method blocks while the library performs the following:
      • Sends a Voice State Update to the Discord Gateway.
      • Waits for a Voice Server Update from the Discord Gateway.
      • Opens a connection to the Discord Voice Gateway.
    3. Gateway Handshake:
      • The Discord Voice Gateway sends a Hello Event, triggering the library to start a *heart.PacemakerLoop for heartbeats.
      • The library sends an Identify or Resume command.
      • The gateway responds with a Ready Event.
    4. UDP & IP Discovery:
      • The library uses the Ready Event info to open a UDP connection to the Voice Server and performs IP Discovery.
      • Once the external IP/port is known, the library sends a Select Protocol Event to the Discord Voice Gateway.
      • The library waits for a Session Description Event.
    5. Active State: Once the session is established, the application can begin sending Speaking Events and Voice Packets (Opus encoded UDP packets).
  4. Run commands-hybrid in Interactions Webhook Mode

    v3

    To run commands-hybrid as a web server using the Discord Interactions Webhook API, you must provide the bot token, a local address to bind the webhook server to, and the application's public key.

    Note that the resulting endpoint (e.g., http://localhost:29485/) must be exposed to the public internet so Discord can send HTTP POST requests to it. Tools like srv.us can be used for this purpose.

    BOT_TOKEN="<token here>" WEBHOOK_ADDR="localhost:29485" WEBHOOK_PUBKEY="<hex app pubkey>" go run .
  5. Run integration tests for arikawa

    v3

    The library includes integration tests that require a valid Discord bot token. To run them, export your token to the BOT_TOKEN environment variable and use the integration build tag.

    export BOT_TOKEN="<BOT_TOKEN>"
    go test -tags integration -race ./...
  6. Send audio using voice.Session

    v3

    Once you have a *voice.Session from JoinChannel(), you can interact with the voice connection:

    1. Set Speaking State: Call (*voice.Session).Speaking(flag) to indicate the application is speaking. Use one of the following flags:
      • voicegateway.Microphone
      • voicegateway.Soundshare
      • voicegateway.Priority
    2. Write Audio: Send Opus-encoded Voice Packets using the (*voice.Session).Write() method. The *voice.Session also implements the standard io.Writer interface.
    3. Stop and Disconnect: To end the session, call (*voice.Session).StopSpeaking(), perform any necessary cleanup (like closing audio streams), and finally call (*voice.Session).Disconnect().
    // Start speaking with a specific flag
    session.Speaking(voicegateway.Soundshare)
    
    // Write audio packets (implements io.Writer)
    _, err := session.Write(opusPacket)
    if err != nil {
        // handle error
    }
    
    // Cleanup
    session.StopSpeaking()
    session.Disconnect()
  7. Choose the right Arikawa package for your project

    v3

    Arikawa is a modular library for building Discord bots or session-based applications. Depending on your requirements for complexity and features, you should choose one of the following abstraction layers:

    • session: The simplest abstraction. It combines the api package and the gateway websocket package. Use this for minimal bots that only need to listen to gateway events.
    • state: Built on top of session. It provides a local cache of API calls and events. Use this if you need a local cache but already have your own command router or don't need one.
    • bot: Built on top of state. It provides a command router based on Go code (similar to discord.py). This is the recommended package for most users as it is the easiest way to build a full bot.
    • voice: Built on top of state. It adds voice support, allowing bots to join voice channels and talk. Note that it uses an io.Writer approach for audio rather than a channel-based approach.
  8. Initialize an HTTP Client

    v3

    To make Discord API requests, use the httputil.Client. You can initialize a default client using NewClient(), which uses the standard httpdriver. Alternatively, use NewClientWithDriver(driver) to inject a custom httpdriver.Client implementation.

    Key configuration fields on the Client struct:

    • Timeout: A time.Duration that sets the deadline for every request. If set to 0 or less, the client won't time out.
    • Retries: The number of attempts to retry a request if it fails with a 5xx error or a 429 (Rate Limit). If set to a value less than 1, the client will retry forever.
    • OnRequest: A slice of RequestOption functions applied to every request.
    • OnResponse: A slice of ResponseFunc functions called after every Do() call.
    import "github.com/diamondburned/arikawa/v3/utils/httputil"
    
    // Create a default client
    client := httputil.NewClient()
    
    // Or customize it
    client.Timeout = 10 * time.Second
    client.Retries = 3
  9. Implement a bare minimum bot with /ping command

    v3

    To create a minimal bot that responds to a /ping interaction, use the cmdroute router, the state library, and the api package. This pattern involves:

    1. Defining command data using api.CreateCommandData.
    2. Creating a router with cmdroute.NewRouter() and registering a handler function.
    3. Initializing a state instance with state.New().
    4. Adding the interaction handler and required intents (e.g., gateway.IntentGuilds) to the state.
    5. Overwriting commands using cmdroute.OverwriteCommands.
    6. Connecting to the gateway with s.Connect().
    package main
    
    import (
    	"context"
    	"log"
    	"os"
    
    	"github.com/diamondburned/arikawa/v3/api"
    	"github.com/diamondburned/arikawa/v3/api/cmdroute"
    	"github.com/diamondburned/arikawa/v3/gateway"
    	"github.com/diamondburned/arikawa/v3/state"
    	"github.com/diamondburned/arikawa/v3/utils/json/option"
    )
    
    var commands = []api.CreateCommandData{{Name: "ping", Description: "Ping!"}}
    
    func main() {
    	r := cmdroute.NewRouter()
    	r.AddFunc("ping", func(ctx context.Context, data cmdroute.CommandData) *api.InteractionResponseData {
    		return &api.InteractionResponseData{Content: option.NewNullableString("Pong!")}
    	})
    
    	s := state.New("Bot " + os.Getenv("BOT_TOKEN"))
    	s.AddInteractionHandler(r)
    	s.AddIntents(gateway.IntentGuilds)
    
    	if err := cmdroute.OverwriteCommands(s, commands); err != nil {
    		log.Fatalln("cannot update commands:", err)
    	}
    
    	if err := s.Connect(context.TODO()); err != nil {
    		log.Println("cannot connect:", err)
    	}
    }
  10. Connect to a voice channel using JoinChannel()

    v3

    To connect to a voice channel, first create a *Voice instance using NewVoice(). Then, call JoinChannel() on that instance.

    Note that JoinChannel() is a blocking operation that follows the full connection handshake. It returns a *voice.Session on success or an error if the connection fails.

    // Assuming voice is the package name
    v := voice.NewVoice()
    session, err := v.JoinChannel(channelID)
    if err != nil {
        // handle error
    }
    // session is now ready for use
  11. Perform fire-and-forget requests with FastRequest

    v3

    Use FastRequest when you need to trigger an API action but do not need to read or process the response body. It performs the request and immediately closes the body for you.

    err := client.FastRequest("POST", "https://discord.com/api/v9/some/endpoint", opts...)
    if err != nil {
    	// handle error
    }