Installation

go get github.com/Its-donkey/kappopher

Basic Example

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/Its-donkey/kappopher/helix"
)

func main() {
    // Create auth client
    authClient := helix.NewAuthClient(helix.AuthConfig{
        ClientID:     "your-client-id",
        ClientSecret: "your-client-secret",
    })

    // Get app access token
    ctx := context.Background()
    token, err := authClient.GetAppAccessToken(ctx)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Got token: %s\n", token.AccessToken)

    // Create Helix client
    client := helix.NewClient("your-client-id", authClient)

    // Get user information
    users, err := client.GetUsers(ctx, &helix.GetUsersParams{
        Logins: []string{"twitchdev"},
    })
    if err != nil {
        log.Fatal(err)
    }

    for _, user := range users.Data {
        fmt.Printf("User: %s (ID: %s)\n", user.DisplayName, user.ID)
    }
}

Authentication

The library supports all four Twitch OAuth flows:

1. Client Credentials Flow (App Access Token)

Best for server-to-server requests that don’t require user permission.

authClient := helix.NewAuthClient(helix.AuthConfig{
    ClientID:     "your-client-id",
    ClientSecret: "your-client-secret",
})

token, err := authClient.GetAppAccessToken(ctx)

2. Authorization Code Flow

Best for server-based apps that can securely store client secrets.

authClient := helix.NewAuthClient(helix.AuthConfig{
    ClientID:     "your-client-id",
    ClientSecret: "your-client-secret",
    RedirectURI:  "http://localhost:3000/callback",
    Scopes:       helix.CommonScopes.Bot,
    State:        "random-state-string",
})

// Step 1: Get authorization URL
url, _ := authClient.GetCodeAuthURL()
// Redirect user to url...

// Step 2: Exchange code for token (after callback)
token, err := authClient.ExchangeCode(ctx, "authorization-code")

3. Implicit Grant Flow

For client-side apps without servers.

authClient := helix.NewAuthClient(helix.AuthConfig{
    ClientID:    "your-client-id",
    RedirectURI: "http://localhost:3000/callback",
    Scopes:      []string{"chat:read", "chat:edit"},
})

url, _ := authClient.GetImplicitAuthURL()
// Redirect user to url...
// Token is returned in URL fragment

4. Device Code Flow

For limited-input devices.

authClient := helix.NewAuthClient(helix.AuthConfig{
    ClientID: "your-client-id",
    Scopes:   []string{"user:read:email"},
})

// Get device code
deviceCode, err := authClient.GetDeviceCode(ctx)
fmt.Printf("Go to %s and enter code: %s\n",
    deviceCode.VerificationURI, deviceCode.UserCode)

// Wait for user authorization
token, err := authClient.WaitForDeviceToken(ctx, deviceCode)

Token Management

// Validate token
validation, err := authClient.ValidateToken(ctx, token.AccessToken)

// Refresh token
newToken, err := authClient.RefreshToken(ctx, token.RefreshToken)

// Revoke token
err := authClient.RevokeToken(ctx, token.AccessToken)

// Auto-refresh (starts background goroutine)
cancel := authClient.AutoRefresh(ctx)
defer cancel()

// Per-request token override (for concurrent multi-user requests)
ctx := helix.WithToken(ctx, &helix.Token{AccessToken: "other-user-token"})
followers, err := client.GetChannelFollowers(ctx, params)

Creating the Helix Client

client := helix.NewClient("client-id", authClient,
    helix.WithHTTPClient(customHTTPClient),  // Optional
    helix.WithBaseURL("custom-url"),         // Optional (for testing)
)

Error Handling

users, err := client.GetUsers(ctx, nil)
if err != nil {
    if apiErr, ok := err.(*helix.APIError); ok {
        fmt.Printf("API Error %d: %s - %s\n",
            apiErr.StatusCode, apiErr.ErrorType, apiErr.Message)
    }
    return err
}

Pagination

var allUsers []helix.User
cursor := ""

for {
    resp, err := client.GetUsers(ctx, &helix.GetUsersParams{
        PaginationParams: &helix.PaginationParams{
            First: 100,
            After: cursor,
        },
    })
    if err != nil {
        return err
    }

    allUsers = append(allUsers, resp.Data...)

    if resp.Pagination == nil || resp.Pagination.Cursor == "" {
        break
    }
    cursor = resp.Pagination.Cursor
}

Rate Limiting

remaining, reset := client.GetRateLimitInfo()
fmt.Printf("Requests remaining: %d, resets at: %s\n", remaining, reset)

Next Steps