Overview
This guide demonstrates how to build a feature-rich chat bot that can:
- Receive and respond to chat messages in real-time
- Execute custom commands (e.g.,
!hello,!dice) - Perform moderation actions (ban, timeout, delete messages)
- Send announcements and shoutouts
- Control chat settings (slow mode, sub-only mode, etc.)
Architecture: The bot uses EventSub WebSocket to receive chat events and the Helix API to send messages and perform actions. This approach provides reliable event delivery with acknowledgment.
Alternative: For lower latency chat handling, see the IRC Client examples.
Prerequisites
Chat bots require user authentication with the following scopes:
chat:read- Read chat messageschat:edit- Send chat messagesmoderator:manage:chat_messages- Delete messages (optional)moderator:manage:banned_users- Ban/timeout users (optional)
Basic Chat Bot
A minimal chat bot that sends a single message to chat. This demonstrates the core setup required for any bot: authentication, client creation, and message sending.
Note: This example only sends messages - it doesn’t receive them. For a fully interactive bot, see the EventSub example below.
package main
import (
"context"
"fmt"
"log"
"strings"
"github.com/Its-donkey/helix/helix"
)
func main() {
ctx := context.Background()
// Create auth client with bot scopes
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
RedirectURI: "http://localhost:3000/callback",
Scopes: helix.CommonScopes.Bot,
})
// Get authorization URL for user to approve
url, _ := authClient.GetCodeAuthURL()
fmt.Printf("Authorize at: %s\n", url)
// After user authorizes, exchange code for token
// token, _ := authClient.ExchangeCode(ctx, "authorization-code")
// Create Helix client
client := helix.NewClient("your-client-id", authClient)
// Bot configuration
broadcasterID := "12345" // Channel to join
botUserID := "67890" // Bot's user ID
// Send a message to chat
err := sendMessage(ctx, client, broadcasterID, botUserID, "Hello chat! Bot is online.")
if err != nil {
log.Fatal(err)
}
}
func sendMessage(ctx context.Context, client *helix.Client, broadcasterID, senderID, message string) error {
_, err := client.SendChatMessage(ctx, &helix.SendChatMessageParams{
BroadcasterID: broadcasterID,
SenderID: senderID,
Message: message,
})
return err
}
Responding to Events with EventSub
A complete chat bot with command handling using EventSub WebSocket. This approach:
- Receives chat messages in real-time via WebSocket
- Parses messages to detect commands (starting with
!) - Executes registered command handlers
- Sends responses back to chat
Key components:
ChatBotstruct: Manages client, configuration, and command registryCommandHandler: Function signature for command implementationsHandleMessage: Parses incoming messages and routes to commandsRegisterCommand: Adds new commands dynamically
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"strings"
"github.com/Its-donkey/helix/helix"
)
type ChatBot struct {
client *helix.Client
broadcasterID string
botUserID string
commands map[string]CommandHandler
}
type CommandHandler func(ctx context.Context, event *helix.ChannelChatMessageEvent) string
func NewChatBot(client *helix.Client, broadcasterID, botUserID string) *ChatBot {
bot := &ChatBot{
client: client,
broadcasterID: broadcasterID,
botUserID: botUserID,
commands: make(map[string]CommandHandler),
}
// Register default commands
bot.RegisterCommand("!hello", func(ctx context.Context, e *helix.ChannelChatMessageEvent) string {
return fmt.Sprintf("Hello @%s!", e.ChatterUserName)
})
bot.RegisterCommand("!uptime", func(ctx context.Context, e *helix.ChannelChatMessageEvent) string {
return "Stream has been live for 2 hours!" // Implement actual uptime check
})
bot.RegisterCommand("!commands", func(ctx context.Context, e *helix.ChannelChatMessageEvent) string {
return "Available commands: !hello, !uptime, !commands"
})
return bot
}
func (b *ChatBot) RegisterCommand(name string, handler CommandHandler) {
b.commands[strings.ToLower(name)] = handler
}
func (b *ChatBot) HandleMessage(ctx context.Context, event *helix.ChannelChatMessageEvent) {
message := strings.TrimSpace(event.Message.Text)
// Check if message is a command
if !strings.HasPrefix(message, "!") {
return
}
// Get command name (first word)
parts := strings.Fields(message)
if len(parts) == 0 {
return
}
cmdName := strings.ToLower(parts[0])
// Find and execute command
if handler, ok := b.commands[cmdName]; ok {
response := handler(ctx, event)
if response != "" {
b.SendMessage(ctx, response)
}
}
}
func (b *ChatBot) SendMessage(ctx context.Context, message string) error {
_, err := b.client.SendChatMessage(ctx, &helix.SendChatMessageParams{
BroadcasterID: b.broadcasterID,
SenderID: b.botUserID,
Message: message,
})
return err
}
func (b *ChatBot) SendReply(ctx context.Context, replyToID, message string) error {
_, err := b.client.SendChatMessage(ctx, &helix.SendChatMessageParams{
BroadcasterID: b.broadcasterID,
SenderID: b.botUserID,
Message: message,
ReplyParentMessageID: replyToID,
})
return err
}
func main() {
ctx := context.Background()
// Setup auth and client (see basic example)
authClient := helix.NewAuthClient(helix.AuthConfig{
ClientID: "your-client-id",
ClientSecret: "your-client-secret",
})
_, _ = authClient.GetAppAccessToken(ctx)
client := helix.NewClient("your-client-id", authClient)
// Create bot
bot := NewChatBot(client, "broadcaster-id", "bot-user-id")
// Add custom command
bot.RegisterCommand("!dice", func(ctx context.Context, e *helix.ChannelChatMessageEvent) string {
return fmt.Sprintf("@%s rolled a %d!", e.ChatterUserName, 1+rand.Intn(6))
})
// Connect to EventSub WebSocket
ws := helix.NewEventSubWebSocket(client)
if err := ws.Connect(ctx); err != nil {
log.Fatal(err)
}
defer ws.Close()
// Subscribe to chat messages
ws.Subscribe(ctx, helix.EventSubTypeChannelChatMessage, "1",
map[string]string{
"broadcaster_user_id": "broadcaster-id",
"user_id": "bot-user-id",
},
func(event json.RawMessage) {
e, err := helix.ParseWSEvent[helix.ChannelChatMessageEvent](event)
if err != nil {
log.Printf("Failed to parse chat message: %v", err)
return
}
bot.HandleMessage(ctx, e)
},
)
fmt.Println("Bot is running...")
select {} // Keep running
}
Moderation Features
Add moderation capabilities to your bot. These require the bot to have moderator privileges in the channel and appropriate scopes.
Required scope: moderator:manage:banned_users for bans/timeouts, moderator:manage:chat_messages for message deletion.
Note: The bot must be a moderator in the channel to perform these actions.
// Timeout a user
func (b *ChatBot) TimeoutUser(ctx context.Context, userID string, duration int, reason string) error {
return b.client.BanUser(ctx, &helix.BanUserParams{
BroadcasterID: b.broadcasterID,
ModeratorID: b.botUserID,
Data: helix.BanUserData{
UserID: userID,
Duration: duration,
Reason: reason,
},
})
}
// Ban a user permanently
func (b *ChatBot) BanUser(ctx context.Context, userID, reason string) error {
return b.client.BanUser(ctx, &helix.BanUserParams{
BroadcasterID: b.broadcasterID,
ModeratorID: b.botUserID,
Data: helix.BanUserData{
UserID: userID,
Reason: reason,
},
})
}
// Unban a user
func (b *ChatBot) UnbanUser(ctx context.Context, userID string) error {
return b.client.UnbanUser(ctx, b.broadcasterID, b.botUserID, userID)
}
// Delete a message
func (b *ChatBot) DeleteMessage(ctx context.Context, messageID string) error {
return b.client.DeleteChatMessages(ctx, &helix.DeleteChatMessagesParams{
BroadcasterID: b.broadcasterID,
ModeratorID: b.botUserID,
MessageID: messageID,
})
}
// Clear chat
func (b *ChatBot) ClearChat(ctx context.Context) error {
return b.client.DeleteChatMessages(ctx, &helix.DeleteChatMessagesParams{
BroadcasterID: b.broadcasterID,
ModeratorID: b.botUserID,
})
}
Announcements
Send highlighted announcements that stand out in chat. Announcements appear with a colored background and are useful for important messages.
Required scope: moderator:manage:announcements
Colors available: "blue", "green", "orange", "purple", or "primary" (channel accent color)
// Send an announcement
func (b *ChatBot) Announce(ctx context.Context, message, color string) error {
return b.client.SendChatAnnouncement(ctx, &helix.SendChatAnnouncementParams{
BroadcasterID: b.broadcasterID,
ModeratorID: b.botUserID,
Message: message,
Color: color, // "blue", "green", "orange", "purple", or "primary"
})
}
Shoutouts
Send a shoutout to promote another streamer. This displays a card in chat with information about the target channel.
Required scope: moderator:manage:shoutouts
Cooldown: There’s a 2-minute cooldown between shoutouts to the same user.
// Give a shoutout to another streamer
func (b *ChatBot) Shoutout(ctx context.Context, targetUserID string) error {
return b.client.SendShoutout(ctx, &helix.SendShoutoutParams{
FromBroadcasterID: b.broadcasterID,
ToBroadcasterID: targetUserID,
ModeratorID: b.botUserID,
})
}
Chat Settings
Control chat modes programmatically. Useful for automating chat management during raids, high-activity moments, or scheduled events.
Required scope: moderator:manage:chat_settings
Available modes:
- Slow mode: Limit how often users can send messages
- Subscriber-only mode: Only subscribers can chat
- Emote-only mode: Only emotes allowed in messages
- Follower-only mode: Only followers can chat (with optional minimum follow time)
// Enable slow mode
func (b *ChatBot) EnableSlowMode(ctx context.Context, seconds int) error {
return b.client.UpdateChatSettings(ctx, &helix.UpdateChatSettingsParams{
BroadcasterID: b.broadcasterID,
ModeratorID: b.botUserID,
SlowMode: boolPtr(true),
SlowModeWaitTime: &seconds,
})
}
// Enable subscriber-only mode
func (b *ChatBot) EnableSubOnlyMode(ctx context.Context) error {
return b.client.UpdateChatSettings(ctx, &helix.UpdateChatSettingsParams{
BroadcasterID: b.broadcasterID,
ModeratorID: b.botUserID,
SubscriberMode: boolPtr(true),
})
}
// Enable emote-only mode
func (b *ChatBot) EnableEmoteOnlyMode(ctx context.Context) error {
return b.client.UpdateChatSettings(ctx, &helix.UpdateChatSettingsParams{
BroadcasterID: b.broadcasterID,
ModeratorID: b.botUserID,
EmoteMode: boolPtr(true),
})
}
func boolPtr(b bool) *bool { return &b }